Merge branch '1.8.x' into chore-specs

This commit is contained in:
Jake Barnby
2025-12-18 09:32:37 +00:00
committed by GitHub
325 changed files with 2355 additions and 1204 deletions
+18 -10
View File
@@ -41,8 +41,6 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
// require controllers after overwriting runtimes // require controllers after overwriting runtimes
require_once __DIR__ . '/controllers/general.php'; require_once __DIR__ . '/controllers/general.php';
Authorization::disable();
CLI::setResource('register', fn () => $register); CLI::setResource('register', fn () => $register);
CLI::setResource('cache', function ($pools) { CLI::setResource('cache', function ($pools) {
@@ -60,7 +58,13 @@ CLI::setResource('pools', function (Registry $register) {
return $register->get('pools'); return $register->get('pools');
}, ['register']); }, ['register']);
CLI::setResource('dbForPlatform', function ($pools, $cache) { CLI::setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$sleep = 3; $sleep = 3;
$maxAttempts = 5; $maxAttempts = 5;
$attempts = 0; $attempts = 0;
@@ -74,6 +78,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
$dbForPlatform = new Database($adapter, $cache); $dbForPlatform = new Database($adapter, $cache);
$dbForPlatform $dbForPlatform
->setAuthorization($authorization)
->setNamespace('_console') ->setNamespace('_console')
->setMetadata('host', \gethostname()) ->setMetadata('host', \gethostname())
->setMetadata('project', 'console'); ->setMetadata('project', 'console');
@@ -99,7 +104,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
} }
return $dbForPlatform; return $dbForPlatform;
}, ['pools', 'cache']); }, ['pools', 'cache', 'authorization']);
CLI::setResource('console', function () { CLI::setResource('console', function () {
return new Document(Config::getParam('console')); return new Document(Config::getParam('console'));
@@ -110,10 +115,10 @@ CLI::setResource(
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
); );
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) { CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) { return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') { if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform; return $dbForPlatform;
} }
@@ -146,6 +151,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
$adapter = new DatabasePool($pools->get($dsn->getHost())); $adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache); $database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database; $databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -162,17 +168,18 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
} }
$database $database
->setAuthorization($authorization)
->setMetadata('host', \gethostname()) ->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId()); ->setMetadata('project', $project->getId());
return $database; return $database;
}; };
}, ['pools', 'dbForPlatform', 'cache']); }, ['pools', 'dbForPlatform', 'cache', 'authorization']);
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) { CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null; $database = null;
return function (?Document $project = null) use ($pools, $cache, $database) { return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') { if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence()); $database->setTenant((int)$project->getSequence());
return $database; return $database;
@@ -182,6 +189,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = new Database($adapter, $cache); $database = new Database($adapter, $cache);
$database $database
->setAuthorization($authorization)
->setSharedTables(true) ->setSharedTables(true)
->setNamespace('logsV1') ->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
@@ -194,7 +202,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database; return $database;
}; };
}, ['pools', 'cache']); }, ['pools', 'cache', 'authorization']);
CLI::setResource('publisher', function (Group $pools) { CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher')); return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']); }, ['pools']);
@@ -187,7 +187,7 @@
<img <img
height="26px" height="26px"
src="{{logoUrl}}" src="{{logoUrl}}"
alt="Appwrite logo" alt="{{platform}} logo"
/> />
</td> </td>
</tr> </tr>
@@ -225,7 +225,7 @@
<tr> <tr>
<td style="padding-left: 4px; padding-right: 4px"> <td style="padding-left: 4px; padding-right: 4px">
<a <a
href="{{twitterUrl}}" href="{{twitter}}"
class="social-icon" class="social-icon"
title="Twitter" title="Twitter"
> >
@@ -234,7 +234,7 @@
</td> </td>
<td style="padding-left: 4px; padding-right: 4px"> <td style="padding-left: 4px; padding-right: 4px">
<a <a
href="{{discordUrl}}" href="{{discord}}"
class="social-icon" class="social-icon"
> >
<img src="https://cloud.appwrite.io/images/mails/discord.png" height="24" width="24" /> <img src="https://cloud.appwrite.io/images/mails/discord.png" height="24" width="24" />
@@ -242,7 +242,7 @@
</td> </td>
<td style="padding-left: 4px; padding-right: 4px"> <td style="padding-left: 4px; padding-right: 4px">
<a <a
href="{{githubUrl}}" href="{{github}}"
class="social-icon" class="social-icon"
> >
<img src="https://cloud.appwrite.io/images/mails/github.png" height="24" width="24" /> <img src="https://cloud.appwrite.io/images/mails/github.png" height="24" width="24" />
@@ -252,15 +252,15 @@
</table> </table>
<table style="width: auto; margin: 0 auto; margin-top: 60px"> <table style="width: auto; margin: 0 auto; margin-top: 60px">
<tr> <tr>
<td><a href="{{termsUrl}}">Terms</a></td> <td><a href="{{terms}}">Terms</a></td>
<td style="color: #e8e9f0"> <td style="color: #e8e9f0">
<div style="margin: 0 8px">|</div> <div style="margin: 0 8px">|</div>
</td> </td>
<td><a href="{{privacyUrl}}">Privacy</a></td> <td><a href="{{privacy}}">Privacy</a></td>
</tr> </tr>
</table> </table>
<p style="text-align: center" align="center"> <p style="text-align: center" align="center">
&copy; {{year}} Appwrite | 251 Little Falls Drive, Wilmington 19808, &copy; {{year}} {{platform}} | 251 Little Falls Drive, Wilmington 19808,
Delaware, United States Delaware, United States
</p> </p>
</div> </div>
+1
View File
@@ -22,4 +22,5 @@ return [
'termsUrl' => APP_EMAIL_TERMS_URL, 'termsUrl' => APP_EMAIL_TERMS_URL,
'privacyUrl' => APP_EMAIL_PRIVACY_URL, 'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'websiteUrl' => 'https://' . APP_DOMAIN, 'websiteUrl' => 'https://' . APP_DOMAIN,
'emailSenderName' => APP_EMAIL_PLATFORM_NAME,
]; ];
+6 -10
View File
@@ -6712,8 +6712,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -6782,7 +6781,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -6799,10 +6798,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
} }
@@ -6904,7 +6900,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -10046,7 +10042,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -10164,7 +10160,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+8 -12
View File
@@ -11516,7 +11516,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -11814,8 +11814,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -11884,7 +11883,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -11901,10 +11900,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
} }
@@ -12006,7 +12002,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -41806,7 +41802,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only column and value pairs to be updated.", "description": "Row data as JSON object. Include only column and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -42166,7 +42162,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -42284,7 +42280,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+8 -12
View File
@@ -11031,7 +11031,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -11333,8 +11333,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -11405,7 +11404,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -11422,10 +11421,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
} }
@@ -11529,7 +11525,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -31514,7 +31510,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only column and value pairs to be updated.", "description": "Row data as JSON object. Include only column and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -31880,7 +31876,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -32000,7 +31996,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+6 -10
View File
@@ -6712,8 +6712,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -6782,7 +6781,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -6799,10 +6798,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
} }
@@ -6904,7 +6900,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -10046,7 +10042,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -10164,7 +10160,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+8 -12
View File
@@ -11516,7 +11516,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -11814,8 +11814,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -11884,7 +11883,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -11901,10 +11900,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
} }
@@ -12006,7 +12002,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -41806,7 +41802,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only column and value pairs to be updated.", "description": "Row data as JSON object. Include only column and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -42166,7 +42162,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -42284,7 +42280,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+8 -12
View File
@@ -11031,7 +11031,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -11333,8 +11333,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -11405,7 +11404,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -11422,10 +11421,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
} }
@@ -11529,7 +11525,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -31514,7 +31510,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only column and value pairs to be updated.", "description": "Row data as JSON object. Include only column and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -31880,7 +31876,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -32000,7 +31996,7 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+7 -11
View File
@@ -6793,8 +6793,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -6856,8 +6855,8 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"default": {}, "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -6876,10 +6875,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
] ]
@@ -6975,7 +6971,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -10049,7 +10045,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -10163,7 +10159,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+9 -13
View File
@@ -11527,7 +11527,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -11815,8 +11815,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -11878,8 +11877,8 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"default": {}, "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -11898,10 +11897,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
] ]
@@ -11997,7 +11993,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -41723,7 +41719,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only column and value pairs to be updated.", "description": "Row data as JSON object. Include only column and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -42067,7 +42063,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -42181,7 +42177,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+9 -13
View File
@@ -11032,7 +11032,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -11324,8 +11324,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -11389,8 +11388,8 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"default": {}, "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -11409,10 +11408,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
] ]
@@ -11510,7 +11506,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -31497,7 +31493,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only column and value pairs to be updated.", "description": "Row data as JSON object. Include only column and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -31847,7 +31843,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -31963,7 +31959,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+7 -11
View File
@@ -6793,8 +6793,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -6856,8 +6855,8 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"default": {}, "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -6876,10 +6875,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
] ]
@@ -6975,7 +6971,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -10049,7 +10045,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -10163,7 +10159,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+9 -13
View File
@@ -11527,7 +11527,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -11815,8 +11815,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -11878,8 +11877,8 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"default": {}, "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -11898,10 +11897,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
] ]
@@ -11997,7 +11993,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -41723,7 +41719,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only column and value pairs to be updated.", "description": "Row data as JSON object. Include only column and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -42067,7 +42063,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -42181,7 +42177,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+9 -13
View File
@@ -11032,7 +11032,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -11324,8 +11324,7 @@
"required": [ "required": [
"databaseId", "databaseId",
"collectionId", "collectionId",
"documentId", "documentId"
"data"
], ],
"responses": [ "responses": [
{ {
@@ -11389,8 +11388,8 @@
"data": { "data": {
"type": "object", "type": "object",
"description": "Document data as JSON object. Include all required attributes of the document to be created or updated.", "description": "Document data as JSON object. Include all required attributes of the document to be created or updated.",
"default": {}, "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -11409,10 +11408,7 @@
"x-example": "<TRANSACTION_ID>", "x-example": "<TRANSACTION_ID>",
"x-nullable": true "x-nullable": true
} }
}, }
"required": [
"data"
]
} }
} }
] ]
@@ -11510,7 +11506,7 @@
"type": "object", "type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.", "description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -31497,7 +31493,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only column and value pairs to be updated.", "description": "Row data as JSON object. Include only column and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"queries": { "queries": {
"type": "array", "type": "array",
@@ -31847,7 +31843,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include all required columns of the row to be created or updated.", "description": "Row data as JSON object. Include all required columns of the row to be created or updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
@@ -31963,7 +31959,7 @@
"type": "object", "type": "object",
"description": "Row data as JSON object. Include only columns and value pairs to be updated.", "description": "Row data as JSON object. Include only columns and value pairs to be updated.",
"default": [], "default": [],
"x-example": "{}" "x-example": "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}"
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
+3 -1
View File
@@ -3,4 +3,6 @@
use Utopia\Image\Image; use Utopia\Image\Image;
use Utopia\System\System; use Utopia\System\System;
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64))); if (\class_exists('Imagick')) {
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
}
+199 -104
View File
@@ -74,7 +74,7 @@ use Utopia\Validator\WhiteList;
$oauthDefaultSuccess = '/console/auth/oauth2/success'; $oauthDefaultSuccess = '/console/auth/oauth2/success';
$oauthDefaultFailure = '/console/auth/oauth2/failure'; $oauthDefaultFailure = '/console/auth/oauth2/failure';
function sendSessionAlert(Locale $locale, Document $user, Document $project, Document $session, Mail $queueForMails) function sendSessionAlert(Locale $locale, Document $user, Document $project, array $platform, Document $session, Mail $queueForMails)
{ {
$subject = $locale->getText("emails.sessionAlert.subject"); $subject = $locale->getText("emails.sessionAlert.subject");
$preview = $locale->getText("emails.sessionAlert.preview"); $preview = $locale->getText("emails.sessionAlert.preview");
@@ -157,13 +157,18 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
$session->setAttribute('clientName', $clientName); $session->setAttribute('clientName', $clientName);
} }
$projectName = $project->getAttribute('name');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$emailVariables = [ $emailVariables = [
'direction' => $locale->getText('settings.direction'), 'direction' => $locale->getText('settings.direction'),
'date' => (new \DateTime())->format('F j'), 'date' => (new \DateTime())->format('F j'),
'year' => (new \DateTime())->format('YYYY'), 'year' => (new \DateTime())->format('YYYY'),
'time' => (new \DateTime())->format('H:i:s'), 'time' => (new \DateTime())->format('H:i:s'),
'user' => $user->getAttribute('name'), 'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'), 'project' => $projectName,
'device' => $session->getAttribute('clientName'), 'device' => $session->getAttribute('clientName'),
'ipAddress' => $session->getAttribute('ip'), 'ipAddress' => $session->getAttribute('ip'),
'country' => $locale->getText('countries.' . $session->getAttribute('countryCode'), $locale->getText('locale.country.unknown')), 'country' => $locale->getText('countries.' . $session->getAttribute('countryCode'), $locale->getText('locale.country.unknown')),
@@ -171,13 +176,14 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$emailVariables = array_merge($emailVariables, [ $emailVariables = array_merge($emailVariables, [
'accentColor' => APP_EMAIL_ACCENT_COLOR, 'accentColor' => $platform['accentColor'],
'logoUrl' => APP_EMAIL_LOGO_URL, 'logoUrl' => $platform['logoUrl'],
'twitterUrl' => APP_SOCIAL_TWITTER, 'twitter' => $platform['twitterUrl'],
'discordUrl' => APP_SOCIAL_DISCORD, 'discord' => $platform['discordUrl'],
'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, 'github' => $platform['githubUrl'],
'termsUrl' => APP_EMAIL_TERMS_URL, 'terms' => $platform['termsUrl'],
'privacyUrl' => APP_EMAIL_PRIVACY_URL, 'privacy' => $platform['privacyUrl'],
'platform' => $platform['platformName'],
]); ]);
} }
@@ -189,15 +195,21 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
->setBody($body) ->setBody($body)
->setBodyTemplate($bodyTemplate) ->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables) ->setVariables($emailVariables)
->setRecipient($email) ->setRecipient($email);
->trigger();
}
;
$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) { // since this is console project, set email sender name!
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
}
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode, Authorization $authorization) {
/** @var Appwrite\Utopia\Database\Documents\User $userFromRequest */ /** @var Appwrite\Utopia\Database\Documents\User $userFromRequest */
$userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); $userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
if ($userFromRequest->isEmpty()) { if ($userFromRequest->isEmpty()) {
throw new Exception(Exception::USER_INVALID_TOKEN); throw new Exception(Exception::USER_INVALID_TOKEN);
@@ -253,7 +265,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
$detector->getDevice() $detector->getDevice()
)); ));
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$session = $dbForProject->createDocument('sessions', $session $session = $dbForProject->createDocument('sessions', $session
->setAttribute('$permissions', [ ->setAttribute('$permissions', [
@@ -262,7 +274,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
Permission::delete(Role::user($user->getId())), Permission::delete(Role::user($user->getId())),
])); ]));
Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId())); $authorization->skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
$dbForProject->purgeCachedDocument('users', $user->getId()); $dbForProject->purgeCachedDocument('users', $user->getId());
// Magic URL + Email OTP // Magic URL + Email OTP
@@ -295,7 +307,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
]) !== 1; ]) !== 1;
if ($isAllowedTokenType && $hasUserEmail && $isSessionAlertsEnabled && $isNotFirstSession) { if ($isAllowedTokenType && $hasUserEmail && $isSessionAlertsEnabled && $isNotFirstSession) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails); sendSessionAlert($locale, $user, $project, $platform, $session, $queueForMails);
} }
$queueForEvents $queueForEvents
@@ -363,8 +375,9 @@ App::post('/v1/account')
->inject('user') ->inject('user')
->inject('project') ->inject('project')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('hooks') ->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) { ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Hooks $hooks) {
$email = \strtolower($email); $email = \strtolower($email);
if ('console' === $project->getId()) { if ('console' === $project->getId()) {
@@ -456,9 +469,9 @@ App::post('/v1/account')
]); ]);
$user->removeAttribute('$sequence'); $user->removeAttribute('$sequence');
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
try { try {
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([ $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
'$permissions' => [ '$permissions' => [
Permission::read(Role::user($user->getId())), Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())), Permission::update(Role::user($user->getId())),
@@ -484,9 +497,9 @@ App::post('/v1/account')
throw new Exception(Exception::USER_ALREADY_EXISTS); throw new Exception(Exception::USER_ALREADY_EXISTS);
} }
Authorization::unsetRole(Role::guests()->toString()); $authorization->removeRole(Role::guests()->toString());
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::users()->toString()); $authorization->addRole(Role::users()->toString());
$response $response
->setStatusCode(Response::STATUS_CODE_CREATED) ->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -953,6 +966,7 @@ App::post('/v1/account/sessions/email')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('locale') ->inject('locale')
->inject('geodb') ->inject('geodb')
->inject('queueForEvents') ->inject('queueForEvents')
@@ -961,7 +975,8 @@ App::post('/v1/account/sessions/email')
->inject('store') ->inject('store')
->inject('proofForPassword') ->inject('proofForPassword')
->inject('proofForToken') ->inject('proofForToken')
->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) { ->inject('authorization')
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
$email = \strtolower($email); $email = \strtolower($email);
$protocol = $request->getProtocol(); $protocol = $request->getProtocol();
@@ -1006,7 +1021,7 @@ App::post('/v1/account/sessions/email')
$detector->getDevice() $detector->getDevice()
)); ));
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
// Re-hash if not using recommended algo // Re-hash if not using recommended algo
if ($user->getAttribute('hash') !== $proofForPassword->getHash()->getName()) { if ($user->getAttribute('hash') !== $proofForPassword->getHash()->getName()) {
@@ -1062,7 +1077,7 @@ App::post('/v1/account/sessions/email')
Query::equal('userId', [$user->getId()]), Query::equal('userId', [$user->getId()]),
]) !== 1 ]) !== 1
) { ) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails); sendSessionAlert($locale, $user, $project, $platform, $session, $queueForMails);
} }
} }
@@ -1105,7 +1120,8 @@ App::post('/v1/account/sessions/anonymous')
->inject('store') ->inject('store')
->inject('proofForPassword') ->inject('proofForPassword')
->inject('proofForToken') ->inject('proofForToken')
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { ->inject('authorization')
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
$protocol = $request->getProtocol(); $protocol = $request->getProtocol();
if ('console' === $project->getId()) { if ('console' === $project->getId()) {
@@ -1150,7 +1166,7 @@ App::post('/v1/account/sessions/anonymous')
'accessedAt' => DateTime::now(), 'accessedAt' => DateTime::now(),
]); ]);
$user->removeAttribute('$sequence'); $user->removeAttribute('$sequence');
Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
// Create session token // Create session token
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG; $duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
@@ -1176,7 +1192,7 @@ App::post('/v1/account/sessions/anonymous')
$detector->getDevice() $detector->getDevice()
)); ));
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [ $session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
Permission::read(Role::user($user->getId())), Permission::read(Role::user($user->getId())),
@@ -1250,6 +1266,7 @@ App::post('/v1/account/sessions/token')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('locale') ->inject('locale')
->inject('geodb') ->inject('geodb')
->inject('queueForEvents') ->inject('queueForEvents')
@@ -1257,6 +1274,7 @@ App::post('/v1/account/sessions/token')
->inject('store') ->inject('store')
->inject('proofForToken') ->inject('proofForToken')
->inject('proofForCode') ->inject('proofForCode')
->inject('authorization')
->action($createSession); ->action($createSession);
App::get('/v1/account/sessions/oauth2/:provider') App::get('/v1/account/sessions/oauth2/:provider')
@@ -1453,7 +1471,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('store') ->inject('store')
->inject('proofForPassword') ->inject('proofForPassword')
->inject('proofForToken') ->inject('proofForToken')
->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) { ->inject('authorization')
->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, Authorization $authorization) use ($oauthDefaultSuccess) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort(); $port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname(); $callbackBase = $protocol . '://' . $request->getHostname();
@@ -1704,7 +1723,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
]); ]);
$user->removeAttribute('$sequence'); $user->removeAttribute('$sequence');
$userDoc = Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); $userDoc = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
$dbForProject->createDocument('targets', new Document([ $dbForProject->createDocument('targets', new Document([
'$permissions' => [ '$permissions' => [
Permission::read(Role::user($user->getId())), Permission::read(Role::user($user->getId())),
@@ -1723,8 +1742,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
} }
} }
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::users()->toString()); $authorization->addRole(Role::users()->toString());
if (false === $user->getAttribute('status')) { // Account is blocked if (false === $user->getAttribute('status')) { // Account is blocked
$failureRedirect(Exception::USER_BLOCKED); // User is in status blocked $failureRedirect(Exception::USER_BLOCKED); // User is in status blocked
@@ -1795,7 +1814,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$dbForProject->updateDocument('users', $user->getId(), $user); $dbForProject->updateDocument('users', $user->getId(), $user);
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$state['success'] = URLParser::parse($state['success']); $state['success'] = URLParser::parse($state['success']);
$query = URLParser::parseQuery($state['success']['query']); $query = URLParser::parseQuery($state['success']['query']);
@@ -1819,7 +1838,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]); ]);
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token $token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [ ->setAttribute('$permissions', [
@@ -2056,7 +2075,8 @@ App::post('/v1/account/tokens/magic-url')
->inject('queueForMails') ->inject('queueForMails')
->inject('proofForPassword') ->inject('proofForPassword')
->inject('platform') ->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) { ->inject('authorization')
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, array $platform, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) { if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
} }
@@ -2129,7 +2149,7 @@ App::post('/v1/account/tokens/magic-url')
]); ]);
$user->removeAttribute('$sequence'); $user->removeAttribute('$sequence');
Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
} }
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); $proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
@@ -2149,7 +2169,7 @@ App::post('/v1/account/tokens/magic-url')
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]); ]);
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token $token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [ ->setAttribute('$permissions', [
@@ -2251,11 +2271,16 @@ App::post('/v1/account/tokens/magic-url')
->setSmtpSenderName($senderName); ->setSmtpSenderName($senderName);
} }
$projectName = $project->getAttribute('name');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$emailVariables = [ $emailVariables = [
'direction' => $locale->getText('settings.direction'), 'direction' => $locale->getText('settings.direction'),
// {{user}}, {{redirect}} and {{project}} are required in default and custom templates // {{user}}, {{redirect}} and {{project}} are required in default and custom templates
'user' => $user->getAttribute('name'), 'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'), 'project' => $projectName,
'redirect' => $url, 'redirect' => $url,
'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN',
'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN',
@@ -2270,8 +2295,13 @@ App::post('/v1/account/tokens/magic-url')
->setPreview($preview) ->setPreview($preview)
->setBody($body) ->setBody($body)
->setVariables($emailVariables) ->setVariables($emailVariables)
->setRecipient($email) ->setRecipient($email);
->trigger();
if ($project->getId() === 'console') {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
$token->setAttribute('secret', $tokenSecret); $token->setAttribute('secret', $tokenSecret);
@@ -2318,13 +2348,15 @@ App::post('/v1/account/tokens/email')
->inject('response') ->inject('response')
->inject('user') ->inject('user')
->inject('project') ->inject('project')
->inject('platform')
->inject('dbForProject') ->inject('dbForProject')
->inject('locale') ->inject('locale')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMails') ->inject('queueForMails')
->inject('proofForPassword') ->inject('proofForPassword')
->inject('proofForCode') ->inject('proofForCode')
->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) { ->inject('authorization')
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) { if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
} }
@@ -2393,9 +2425,9 @@ App::post('/v1/account/tokens/email')
]); ]);
$user->removeAttribute('$sequence'); $user->removeAttribute('$sequence');
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
try { try {
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([ $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
'$permissions' => [ '$permissions' => [
Permission::read(Role::user($user->getId())), Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())), Permission::update(Role::user($user->getId())),
@@ -2433,7 +2465,7 @@ App::post('/v1/account/tokens/email')
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]); ]);
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token $token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [ ->setAttribute('$permissions', [
@@ -2525,12 +2557,17 @@ App::post('/v1/account/tokens/email')
->setSmtpSenderName($senderName); ->setSmtpSenderName($senderName);
} }
$projectName = $project->getAttribute('name');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$emailVariables = [ $emailVariables = [
'heading' => $heading, 'heading' => $heading,
'direction' => $locale->getText('settings.direction'), 'direction' => $locale->getText('settings.direction'),
// {{user}}, {{project}} and {{otp}} are required in the templates // {{user}}, {{project}} and {{otp}} are required in the templates
'user' => $user->getAttribute('name'), 'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'), 'project' => $projectName,
'otp' => $tokenSecret, 'otp' => $tokenSecret,
'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN',
'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN',
@@ -2542,13 +2579,14 @@ App::post('/v1/account/tokens/email')
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$emailVariables = array_merge($emailVariables, [ $emailVariables = array_merge($emailVariables, [
'accentColor' => APP_EMAIL_ACCENT_COLOR, 'accentColor' => $platform['accentColor'],
'logoUrl' => APP_EMAIL_LOGO_URL, 'logoUrl' => $platform['logoUrl'],
'twitterUrl' => APP_SOCIAL_TWITTER, 'twitter' => $platform['twitterUrl'],
'discordUrl' => APP_SOCIAL_DISCORD, 'discord' => $platform['discordUrl'],
'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, 'github' => $platform['githubUrl'],
'termsUrl' => APP_EMAIL_TERMS_URL, 'terms' => $platform['termsUrl'],
'privacyUrl' => APP_EMAIL_PRIVACY_URL, 'privacy' => $platform['privacyUrl'],
'platform' => $platform['platformName'],
]); ]);
} }
@@ -2558,8 +2596,14 @@ App::post('/v1/account/tokens/email')
->setBody($body) ->setBody($body)
->setBodyTemplate($bodyTemplate) ->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables) ->setVariables($emailVariables)
->setRecipient($email) ->setRecipient($email);
->trigger();
// since this is console project, set email sender name!
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
$token->setAttribute('secret', $tokenSecret); $token->setAttribute('secret', $tokenSecret);
@@ -2610,16 +2654,18 @@ App::put('/v1/account/sessions/magic-url')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('locale') ->inject('locale')
->inject('geodb') ->inject('geodb')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMails') ->inject('queueForMails')
->inject('store') ->inject('store')
->inject('proofForCode') ->inject('proofForCode')
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) { ->inject('authorization')
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode, $authorization) use ($createSession) {
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL); $proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
$proofForToken->setHash(new Sha()); $proofForToken->setHash(new Sha());
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode); $createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode, $authorization);
}); });
App::put('/v1/account/sessions/phone') App::put('/v1/account/sessions/phone')
@@ -2657,6 +2703,7 @@ App::put('/v1/account/sessions/phone')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('locale') ->inject('locale')
->inject('geodb') ->inject('geodb')
->inject('queueForEvents') ->inject('queueForEvents')
@@ -2664,6 +2711,7 @@ App::put('/v1/account/sessions/phone')
->inject('store') ->inject('store')
->inject('proofForToken') ->inject('proofForToken')
->inject('proofForCode') ->inject('proofForCode')
->inject('authorization')
->action($createSession); ->action($createSession);
App::post('/v1/account/tokens/phone') App::post('/v1/account/tokens/phone')
@@ -2697,6 +2745,7 @@ App::post('/v1/account/tokens/phone')
->inject('response') ->inject('response')
->inject('user') ->inject('user')
->inject('project') ->inject('project')
->inject('platform')
->inject('dbForProject') ->inject('dbForProject')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMessaging') ->inject('queueForMessaging')
@@ -2706,7 +2755,8 @@ App::post('/v1/account/tokens/phone')
->inject('plan') ->inject('plan')
->inject('store') ->inject('store')
->inject('proofForCode') ->inject('proofForCode')
->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) { ->inject('authorization')
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
} }
@@ -2756,9 +2806,9 @@ App::post('/v1/account/tokens/phone')
]); ]);
$user->removeAttribute('$sequence'); $user->removeAttribute('$sequence');
Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
try { try {
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([ $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
'$permissions' => [ '$permissions' => [
Permission::read(Role::user($user->getId())), Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())), Permission::update(Role::user($user->getId())),
@@ -2804,7 +2854,7 @@ App::post('/v1/account/tokens/phone')
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]); ]);
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token $token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [ ->setAttribute('$permissions', [
@@ -2823,9 +2873,14 @@ App::post('/v1/account/tokens/phone')
$message = $customTemplate['message'] ?? $message; $message = $customTemplate['message'] ?? $message;
} }
$projectName = $project->getAttribute('name');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$messageContent = Template::fromString($locale->getText("sms.verification.body")); $messageContent = Template::fromString($locale->getText("sms.verification.body"));
$messageContent $messageContent
->setParam('{{project}}', $project->getAttribute('name')) ->setParam('{{project}}', $projectName)
->setParam('{{secret}}', $secret); ->setParam('{{secret}}', $secret);
$messageContent = \strip_tags($messageContent->render()); $messageContent = \strip_tags($messageContent->render());
$message = $message->setParam('{{token}}', $messageContent); $message = $message->setParam('{{token}}', $messageContent);
@@ -3186,7 +3241,8 @@ App::patch('/v1/account/email')
->inject('project') ->inject('project')
->inject('hooks') ->inject('hooks')
->inject('proofForPassword') ->inject('proofForPassword')
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) { ->inject('authorization')
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword, Authorization $authorization) {
// passwordUpdate will be empty if the user has never set a password // passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate'); $passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -3238,7 +3294,7 @@ App::patch('/v1/account/email')
->setAttribute('passwordUpdate', DateTime::now()); ->setAttribute('passwordUpdate', DateTime::now());
} }
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [ $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]), Query::equal('identifier', [$email]),
])); ]));
@@ -3254,7 +3310,7 @@ App::patch('/v1/account/email')
$oldTarget = $user->find('identifier', $oldEmail, 'targets'); $oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
} }
$dbForProject->purgeCachedDocument('users', $user->getId()); $dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Duplicate) { } catch (Duplicate) {
@@ -3295,8 +3351,9 @@ App::patch('/v1/account/phone')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('project') ->inject('project')
->inject('hooks') ->inject('hooks')
->inject('proofForPassword') ->inject('proofForPassword')
->action(function (string $phone, string $password, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) { ->inject('authorization')
->action(function (string $phone, string $password, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword, Authorization $authorization) {
// passwordUpdate will be empty if the user has never set a password // passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate'); $passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -3311,7 +3368,7 @@ App::patch('/v1/account/phone')
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [ $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]), Query::equal('identifier', [$phone]),
])); ]));
@@ -3342,7 +3399,7 @@ App::patch('/v1/account/phone')
$oldTarget = $user->find('identifier', $oldPhone, 'targets'); $oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
} }
$dbForProject->purgeCachedDocument('users', $user->getId()); $dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Duplicate $th) { } catch (Duplicate $th) {
@@ -3473,15 +3530,18 @@ App::post('/v1/account/recovery')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('locale') ->inject('locale')
->inject('queueForMails') ->inject('queueForMails')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('proofForToken') ->inject('proofForToken')
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken) { ->inject('authorization')
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) { if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
} }
$url = htmlentities($url); $url = htmlentities($url);
$email = \strtolower($email); $email = \strtolower($email);
@@ -3513,7 +3573,7 @@ App::post('/v1/account/recovery')
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]); ]);
Authorization::setRole(Role::user($profile->getId())->toString()); $authorization->addRole(Role::user($profile->getId())->toString());
$recovery = $dbForProject->createDocument('tokens', $recovery $recovery = $dbForProject->createDocument('tokens', $recovery
->setAttribute('$permissions', [ ->setAttribute('$permissions', [
@@ -3528,7 +3588,14 @@ App::post('/v1/account/recovery')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
$url = Template::unParseURL($url); $url = Template::unParseURL($url);
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]'); $projectName = $project->isEmpty()
? 'Console'
: $project->getAttribute('name', '[APP-NAME]');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$body = $locale->getText("emails.recovery.body"); $body = $locale->getText("emails.recovery.body");
$subject = $locale->getText("emails.recovery.subject"); $subject = $locale->getText("emails.recovery.subject");
$preview = $locale->getText("emails.recovery.preview"); $preview = $locale->getText("emails.recovery.preview");
@@ -3606,8 +3673,13 @@ App::post('/v1/account/recovery')
->setBody($body) ->setBody($body)
->setVariables($emailVariables) ->setVariables($emailVariables)
->setSubject($subject) ->setSubject($subject)
->setPreview($preview) ->setPreview($preview);
->trigger();
if ($project->getId() === 'console') {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
$recovery->setAttribute('secret', $secret); $recovery->setAttribute('secret', $secret);
@@ -3657,7 +3729,8 @@ App::put('/v1/account/recovery')
->inject('hooks') ->inject('hooks')
->inject('proofForPassword') ->inject('proofForPassword')
->inject('proofForToken') ->inject('proofForToken')
->action(function (string $userId, string $secret, string $password, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken) { ->inject('authorization')
->action(function (string $userId, string $secret, string $password, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
/** @var Appwrite\Utopia\Database\Documents\User $profile */ /** @var Appwrite\Utopia\Database\Documents\User $profile */
$profile = $dbForProject->getDocument('users', $userId); $profile = $dbForProject->getDocument('users', $userId);
@@ -3671,7 +3744,7 @@ App::put('/v1/account/recovery')
throw new Exception(Exception::USER_INVALID_TOKEN); throw new Exception(Exception::USER_INVALID_TOKEN);
} }
Authorization::setRole(Role::user($profile->getId())->toString()); $authorization->addRole(Role::user($profile->getId())->toString());
$newPassword = $proofForPassword->hash($password); $newPassword = $proofForPassword->hash($password);
@@ -3767,13 +3840,15 @@ App::post('/v1/account/verifications/email')
->inject('request') ->inject('request')
->inject('response') ->inject('response')
->inject('project') ->inject('project')
->inject('platform')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('locale') ->inject('locale')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMails') ->inject('queueForMails')
->inject('proofForToken') ->inject('proofForToken')
->action(function (string $url, Request $request, Response $response, Document $project, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken) { ->inject('authorization')
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) { if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
@@ -3802,7 +3877,7 @@ App::post('/v1/account/verifications/email')
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]); ]);
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$verification = $dbForProject->createDocument('tokens', $verification $verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$permissions', [ ->setAttribute('$permissions', [
@@ -3817,7 +3892,15 @@ App::post('/v1/account/verifications/email')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]);
$url = Template::unParseURL($url); $url = Template::unParseURL($url);
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]'); $projectName = $project->isEmpty()
? 'Console'
: $project->getAttribute('name', '[APP-NAME]');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$body = $locale->getText("emails.verification.body"); $body = $locale->getText("emails.verification.body");
$preview = $locale->getText("emails.verification.preview"); $preview = $locale->getText("emails.verification.preview");
$subject = $locale->getText("emails.verification.subject"); $subject = $locale->getText("emails.verification.subject");
@@ -3903,13 +3986,14 @@ App::post('/v1/account/verifications/email')
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$emailVariables = array_merge($emailVariables, [ $emailVariables = array_merge($emailVariables, [
'accentColor' => APP_EMAIL_ACCENT_COLOR, 'accentColor' => $platform['accentColor'],
'logoUrl' => APP_EMAIL_LOGO_URL, 'logoUrl' => $platform['logoUrl'],
'twitterUrl' => APP_SOCIAL_TWITTER, 'twitter' => $platform['twitterUrl'],
'discordUrl' => APP_SOCIAL_DISCORD, 'discord' => $platform['discordUrl'],
'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, 'github' => $platform['githubUrl'],
'termsUrl' => APP_EMAIL_TERMS_URL, 'terms' => $platform['termsUrl'],
'privacyUrl' => APP_EMAIL_PRIVACY_URL, 'privacy' => $platform['privacyUrl'],
'platform' => $platform['platformName'],
]); ]);
} }
@@ -3920,8 +4004,13 @@ App::post('/v1/account/verifications/email')
->setBodyTemplate($bodyTemplate) ->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables) ->setVariables($emailVariables)
->setRecipient($user->getAttribute('email')) ->setRecipient($user->getAttribute('email'))
->setName($user->getAttribute('name') ?? '') ->setName($user->getAttribute('name') ?? '');
->trigger();
if ($project->getId() === 'console') {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
$verification->setAttribute('secret', $verificationSecret); $verification->setAttribute('secret', $verificationSecret);
@@ -3987,9 +4076,10 @@ App::put('/v1/account/verifications/email')
->inject('dbForProject') ->inject('dbForProject')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('proofForToken') ->inject('proofForToken')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken) { ->inject('authorization')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
/** @var Appwrite\Utopia\Database\Documents\User $profile */ /** @var Appwrite\Utopia\Database\Documents\User $profile */
$profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) { if ($profile->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND); throw new Exception(Exception::USER_NOT_FOUND);
@@ -4001,7 +4091,7 @@ App::put('/v1/account/verifications/email')
throw new Exception(Exception::USER_INVALID_TOKEN); throw new Exception(Exception::USER_INVALID_TOKEN);
} }
Authorization::setRole(Role::user($profile->getId())->toString()); $authorization->addRole(Role::user($profile->getId())->toString());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
@@ -4061,7 +4151,8 @@ App::post('/v1/account/verifications/phone')
->inject('queueForStatsUsage') ->inject('queueForStatsUsage')
->inject('plan') ->inject('plan')
->inject('proofForCode') ->inject('proofForCode')
->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) { ->inject('authorization')
->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, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
} }
@@ -4100,7 +4191,7 @@ App::post('/v1/account/verifications/phone')
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]); ]);
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$verification = $dbForProject->createDocument('tokens', $verification $verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$permissions', [ ->setAttribute('$permissions', [
@@ -4206,9 +4297,10 @@ App::put('/v1/account/verifications/phone')
->inject('dbForProject') ->inject('dbForProject')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('proofForCode') ->inject('proofForCode')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode) { ->inject('authorization')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode, Authorization $authorization) {
/** @var Appwrite\Utopia\Database\Documents\User $profile */ /** @var Appwrite\Utopia\Database\Documents\User $profile */
$profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) { if ($profile->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND); throw new Exception(Exception::USER_NOT_FOUND);
@@ -4220,7 +4312,7 @@ App::put('/v1/account/verifications/phone')
throw new Exception(Exception::USER_INVALID_TOKEN); throw new Exception(Exception::USER_INVALID_TOKEN);
} }
Authorization::setRole(Role::user($profile->getId())->toString()); $authorization->addRole(Role::user($profile->getId())->toString());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true)); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
@@ -4273,12 +4365,13 @@ App::post('/v1/account/targets/push')
->inject('dbForProject') ->inject('dbForProject')
->inject('store') ->inject('store')
->inject('proofForToken') ->inject('proofForToken')
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken) { ->inject('authorization')
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken, Authorization $authorization) {
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId; $targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
$provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); $provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if (!$target->isEmpty()) { if (!$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
@@ -4353,9 +4446,10 @@ App::put('/v1/account/targets/:targetId/push')
->inject('request') ->inject('request')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { ->inject('authorization')
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) { if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND); throw new Exception(Exception::USER_TARGET_NOT_FOUND);
@@ -4418,8 +4512,9 @@ App::delete('/v1/account/targets/:targetId/push')
->inject('request') ->inject('request')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject) { ->inject('authorization')
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) { if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND); throw new Exception(Exception::USER_TARGET_NOT_FOUND);
+16 -13
View File
@@ -70,9 +70,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
unset($image); unset($image);
}; };
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger) { $getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, Authorization $authorization, ?Logger $logger) {
try { try {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); $user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []); $sessions = $user->getAttribute('sessions', []);
@@ -123,7 +123,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''))); ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession)); $authorization->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
$dbForProject->purgeCachedDocument('users', $user->getId()); $dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Throwable $err) { } catch (Throwable $err) {
@@ -131,7 +131,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
do { do {
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken'); $previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); $user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []); $sessions = $user->getAttribute('sessions', []);
$gitHubSession = new Document(); $gitHubSession = new Document();
@@ -841,8 +841,9 @@ App::get('/v1/cards/cloud')
->inject('contributors') ->inject('contributors')
->inject('employees') ->inject('employees')
->inject('logger') ->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { ->inject('authorization')
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) { if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND); throw new Exception(Exception::USER_NOT_FOUND);
@@ -853,7 +854,7 @@ App::get('/v1/cards/cloud')
$email = $user->getAttribute('email', ''); $email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt()); $createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger); $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubName = $gitHub['name'] ?? ''; $githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? ''; $githubId = $gitHub['id'] ?? '';
@@ -1048,8 +1049,9 @@ App::get('/v1/cards/cloud-back')
->inject('contributors') ->inject('contributors')
->inject('employees') ->inject('employees')
->inject('logger') ->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { ->inject('authorization')
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) { if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND); throw new Exception(Exception::USER_NOT_FOUND);
@@ -1059,7 +1061,7 @@ App::get('/v1/cards/cloud-back')
$userId = $user->getId(); $userId = $user->getId();
$email = $user->getAttribute('email', ''); $email = $user->getAttribute('email', '');
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger); $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubId = $gitHub['id'] ?? ''; $githubId = $gitHub['id'] ?? '';
$isHero = \array_key_exists($email, $heroes); $isHero = \array_key_exists($email, $heroes);
@@ -1126,8 +1128,9 @@ App::get('/v1/cards/cloud-og')
->inject('contributors') ->inject('contributors')
->inject('employees') ->inject('employees')
->inject('logger') ->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { ->inject('authorization')
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) { if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND); throw new Exception(Exception::USER_NOT_FOUND);
@@ -1142,7 +1145,7 @@ App::get('/v1/cards/cloud-og')
$email = $user->getAttribute('email', ''); $email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt()); $createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger); $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubName = $gitHub['name'] ?? ''; $githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? ''; $githubId = $gitHub['id'] ?? '';
+3 -2
View File
@@ -28,11 +28,12 @@ use Utopia\Validator\Text;
App::init() App::init()
->groups(['graphql']) ->groups(['graphql'])
->inject('project') ->inject('project')
->action(function (Document $project) { ->inject('authorization')
->action(function (Document $project, Authorization $authorization) {
if ( if (
array_key_exists('graphql', $project->getAttribute('apis', [])) array_key_exists('graphql', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['graphql'] && !$project->getAttribute('apis', [])['graphql']
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles())) && !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) { ) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
} }
+7 -5
View File
@@ -120,7 +120,7 @@ App::get('/v1/health/db')
$output[] = new Document([ $output[] = new Document([
'name' => $key . " ($database)", 'name' => $key . " ($database)",
'status' => 'pass', 'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000) 'ping' => \round((\microtime(true) - $checkStart) * 1000)
]); ]);
} else { } else {
$failures[] = $database; $failures[] = $database;
@@ -131,6 +131,8 @@ App::get('/v1/health/db')
} }
} }
// Only throw error if ALL databases failed (no successful pings)
// This allows partial failures in environments where not all DBs are ready
if (!empty($failures)) { if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failures)); throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failures));
} }
@@ -180,7 +182,7 @@ App::get('/v1/health/cache')
$output[] = new Document([ $output[] = new Document([
'name' => $key . " ($cache)", 'name' => $key . " ($cache)",
'status' => 'pass', 'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000) 'ping' => \round((\microtime(true) - $checkStart) * 1000)
]); ]);
} else { } else {
$failures[] = $cache; $failures[] = $cache;
@@ -240,7 +242,7 @@ App::get('/v1/health/pubsub')
$output[] = new Document([ $output[] = new Document([
'name' => $key . " ($pubsub)", 'name' => $key . " ($pubsub)",
'status' => 'pass', 'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000) 'ping' => \round((\microtime(true) - $checkStart) * 1000)
]); ]);
} else { } else {
$failures[] = $pubsub; $failures[] = $pubsub;
@@ -822,7 +824,7 @@ App::get('/v1/health/storage/local')
$output = [ $output = [
'status' => 'pass', 'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000) 'ping' => \round((\microtime(true) - $checkStart) * 1000)
]; ];
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS); $response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
@@ -874,7 +876,7 @@ App::get('/v1/health/storage')
$output = [ $output = [
'status' => 'pass', 'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000) 'ping' => \round((\microtime(true) - $checkStart) * 1000)
]; ];
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS); $response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
+35 -30
View File
@@ -36,6 +36,7 @@ use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\ID;
use Utopia\Database\Query; use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Cursor;
@@ -1073,8 +1074,9 @@ App::get('/v1/messaging/providers')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('response') ->inject('response')
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) { ->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
try { try {
$queries = Query::parseQueries($queries); $queries = Query::parseQueries($queries);
} catch (QueryException $e) { } catch (QueryException $e) {
@@ -1100,7 +1102,7 @@ App::get('/v1/messaging/providers')
} }
$providerId = $cursor->getValue(); $providerId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
if ($cursorDocument->isEmpty()) { if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found."); throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found.");
@@ -2477,8 +2479,9 @@ App::get('/v1/messaging/topics')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('response') ->inject('response')
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) { ->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
try { try {
$queries = Query::parseQueries($queries); $queries = Query::parseQueries($queries);
} catch (QueryException $e) { } catch (QueryException $e) {
@@ -2504,7 +2507,7 @@ App::get('/v1/messaging/topics')
} }
$topicId = $cursor->getValue(); $topicId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($cursorDocument->isEmpty()) { if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found."); throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found.");
@@ -2774,29 +2777,27 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
->param('targetId', '', new UID(), 'Target ID. The target ID to link to the specified Topic ID.') ->param('targetId', '', new UID(), 'Target ID. The target ID to link to the specified Topic ID.')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('response') ->inject('response')
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) { ->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
$subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId; $subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId;
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) { if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND); throw new Exception(Exception::TOPIC_NOT_FOUND);
} }
if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) {
$validator = new Authorization('subscribe'); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
if (!$validator->isValid($topic->getAttribute('subscribe'))) {
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
} }
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) { if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND); throw new Exception(Exception::USER_TARGET_NOT_FOUND);
} }
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$subscriber = new Document([ $subscriber = new Document([
'$id' => $subscriberId, '$id' => $subscriberId,
@@ -2829,7 +2830,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE), default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
}; };
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute( $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute(
'topics', 'topics',
$topicId, $topicId,
$totalAttribute, $totalAttribute,
@@ -2874,8 +2875,9 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('response') ->inject('response')
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) { ->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
try { try {
$queries = Query::parseQueries($queries); $queries = Query::parseQueries($queries);
} catch (QueryException $e) { } catch (QueryException $e) {
@@ -2886,7 +2888,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
$queries[] = Query::search('search', $search); $queries[] = Query::search('search', $search);
} }
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) { if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND); throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -2909,7 +2911,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
} }
$subscriberId = $cursor->getValue(); $subscriberId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId)); $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
if ($cursorDocument->isEmpty()) { if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found."); throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found.");
@@ -2923,10 +2925,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
} }
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) { $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
return function () use ($subscriber, $dbForProject) { return function () use ($subscriber, $dbForProject, $authorization) {
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
return $subscriber return $subscriber
->setAttribute('target', $target) ->setAttribute('target', $target)
@@ -3055,9 +3057,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.') ->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
->param('subscriberId', '', new UID(), 'Subscriber ID.') ->param('subscriberId', '', new UID(), 'Subscriber ID.')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('response') ->inject('response')
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) { ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Authorization $authorization, Response $response) {
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) { if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND); throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -3069,8 +3072,8 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
throw new Exception(Exception::SUBSCRIBER_NOT_FOUND); throw new Exception(Exception::SUBSCRIBER_NOT_FOUND);
} }
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$subscriber $subscriber
->setAttribute('target', $target) ->setAttribute('target', $target)
@@ -3106,9 +3109,10 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->param('subscriberId', '', new UID(), 'Subscriber ID.') ->param('subscriberId', '', new UID(), 'Subscriber ID.')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('response') ->inject('response')
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) { ->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) { if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND); throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -3131,7 +3135,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE), default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
}; };
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute( $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute(
'topics', 'topics',
$topicId, $topicId,
$totalAttribute, $totalAttribute,
@@ -3690,8 +3694,9 @@ App::get('/v1/messaging/messages')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('response') ->inject('response')
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) { ->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
try { try {
$queries = Query::parseQueries($queries); $queries = Query::parseQueries($queries);
} catch (QueryException $e) { } catch (QueryException $e) {
@@ -3717,7 +3722,7 @@ App::get('/v1/messaging/messages')
} }
$messageId = $cursor->getValue(); $messageId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId)); $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('messages', $messageId));
if ($cursorDocument->isEmpty()) { if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found."); throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found.");
+30 -10
View File
@@ -69,10 +69,11 @@ App::post('/v1/migrations/appwrite')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('user') ->inject('user')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMigrations') ->inject('queueForMigrations')
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) { ->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([ $migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(), '$id' => ID::unique(),
'status' => 'pending', 'status' => 'pending',
@@ -96,6 +97,7 @@ App::post('/v1/migrations/appwrite')
$queueForMigrations $queueForMigrations
->setMigration($migration) ->setMigration($migration)
->setProject($project) ->setProject($project)
->setPlatform($platform)
->setUser($user) ->setUser($user)
->trigger(); ->trigger();
@@ -128,10 +130,11 @@ App::post('/v1/migrations/firebase')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('user') ->inject('user')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMigrations') ->inject('queueForMigrations')
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) { ->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$serviceAccountData = json_decode($serviceAccount, true); $serviceAccountData = json_decode($serviceAccount, true);
if (empty($serviceAccountData)) { if (empty($serviceAccountData)) {
@@ -163,6 +166,7 @@ App::post('/v1/migrations/firebase')
$queueForMigrations $queueForMigrations
->setMigration($migration) ->setMigration($migration)
->setProject($project) ->setProject($project)
->setPlatform($platform)
->setUser($user) ->setUser($user)
->trigger(); ->trigger();
@@ -200,10 +204,11 @@ App::post('/v1/migrations/supabase')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('user') ->inject('user')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMigrations') ->inject('queueForMigrations')
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) { ->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([ $migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(), '$id' => ID::unique(),
'status' => 'pending', 'status' => 'pending',
@@ -230,6 +235,7 @@ App::post('/v1/migrations/supabase')
$queueForMigrations $queueForMigrations
->setMigration($migration) ->setMigration($migration)
->setProject($project) ->setProject($project)
->setPlatform($platform)
->setUser($user) ->setUser($user)
->trigger(); ->trigger();
@@ -268,10 +274,11 @@ App::post('/v1/migrations/nhost')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('user') ->inject('user')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMigrations') ->inject('queueForMigrations')
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) { ->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([ $migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(), '$id' => ID::unique(),
'status' => 'pending', 'status' => 'pending',
@@ -299,6 +306,7 @@ App::post('/v1/migrations/nhost')
$queueForMigrations $queueForMigrations
->setMigration($migration) ->setMigration($migration)
->setProject($project) ->setProject($project)
->setPlatform($platform)
->setUser($user) ->setUser($user)
->trigger(); ->trigger();
@@ -334,7 +342,9 @@ App::post('/v1/migrations/csv/imports')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('authorization')
->inject('project') ->inject('project')
->inject('platform')
->inject('deviceForFiles') ->inject('deviceForFiles')
->inject('deviceForMigrations') ->inject('deviceForMigrations')
->inject('queueForEvents') ->inject('queueForEvents')
@@ -347,13 +357,15 @@ App::post('/v1/migrations/csv/imports')
Response $response, Response $response,
Database $dbForProject, Database $dbForProject,
Database $dbForPlatform, Database $dbForPlatform,
Authorization $authorization,
Document $project, Document $project,
array $platform,
Device $deviceForFiles, Device $deviceForFiles,
Device $deviceForMigrations, Device $deviceForMigrations,
Event $queueForEvents, Event $queueForEvents,
Migration $queueForMigrations Migration $queueForMigrations
) { ) {
$bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) { $bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
if ($internalFile) { if ($internalFile) {
return $dbForPlatform->getDocument('buckets', 'default'); return $dbForPlatform->getDocument('buckets', 'default');
} }
@@ -364,7 +376,7 @@ App::post('/v1/migrations/csv/imports')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
$file = Authorization::skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if ($file->isEmpty()) { if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
} }
@@ -441,6 +453,7 @@ App::post('/v1/migrations/csv/imports')
$queueForMigrations $queueForMigrations
->setMigration($migration) ->setMigration($migration)
->setProject($project) ->setProject($project)
->setProject($project)
->trigger(); ->trigger();
$response $response
@@ -480,7 +493,9 @@ App::post('/v1/migrations/csv/exports')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('authorization')
->inject('project') ->inject('project')
->inject('platform')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMigrations') ->inject('queueForMigrations')
->action(function ( ->action(function (
@@ -497,7 +512,9 @@ App::post('/v1/migrations/csv/exports')
Response $response, Response $response,
Database $dbForProject, Database $dbForProject,
Database $dbForPlatform, Database $dbForPlatform,
Authorization $authorization,
Document $project, Document $project,
array $platform,
Event $queueForEvents, Event $queueForEvents,
Migration $queueForMigrations Migration $queueForMigrations
) { ) {
@@ -507,7 +524,7 @@ App::post('/v1/migrations/csv/exports')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
} }
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'default')); $bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
if ($bucket->isEmpty()) { if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
@@ -520,12 +537,12 @@ App::post('/v1/migrations/csv/exports')
throw new Exception(Exception::COLLECTION_NOT_FOUND); throw new Exception(Exception::COLLECTION_NOT_FOUND);
} }
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) { if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND); throw new Exception(Exception::DATABASE_NOT_FOUND);
} }
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId)); $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
if ($collection->isEmpty()) { if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND); throw new Exception(Exception::COLLECTION_NOT_FOUND);
} }
@@ -571,6 +588,7 @@ App::post('/v1/migrations/csv/exports')
$queueForMigrations $queueForMigrations
->setMigration($migration) ->setMigration($migration)
->setProject($project) ->setProject($project)
->setPlatform($platform)
->trigger(); ->trigger();
$response $response
@@ -903,9 +921,10 @@ App::patch('/v1/migrations/:migrationId')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('platform')
->inject('user') ->inject('user')
->inject('queueForMigrations') ->inject('queueForMigrations')
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, Document $user, Migration $queueForMigrations) { ->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Migration $queueForMigrations) {
$migration = $dbForProject->getDocument('migrations', $migrationId); $migration = $dbForProject->getDocument('migrations', $migrationId);
if ($migration->isEmpty()) { if ($migration->isEmpty()) {
@@ -924,6 +943,7 @@ App::patch('/v1/migrations/:migrationId')
$queueForMigrations $queueForMigrations
->setMigration($migration) ->setMigration($migration)
->setProject($project) ->setProject($project)
->setPlatform($platform)
->setUser($user) ->setUser($user)
->trigger(); ->trigger();
+5 -4
View File
@@ -45,9 +45,10 @@ App::get('/v1/project/usage')
->inject('response') ->inject('response')
->inject('project') ->inject('project')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('getLogsDB') ->inject('getLogsDB')
->inject('smsRates') ->inject('smsRates')
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) { ->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, Authorization $authorization, callable $getLogsDB, array $smsRates) {
$stats = $total = $usage = []; $stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00'; $format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format); $firstDay = (new DateTime($startDate))->format($format);
@@ -102,7 +103,7 @@ App::get('/v1/project/usage')
'1d' => 'Y-m-d\T00:00:00.000P', '1d' => 'Y-m-d\T00:00:00.000P',
}; };
Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) { $authorization->skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
foreach ($metrics['total'] as $metric) { foreach ($metrics['total'] as $metric) {
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject; $db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
@@ -286,7 +287,7 @@ App::get('/v1/project/usage')
}, $dbForProject->find('functions')); }, $dbForProject->find('functions'));
// This total is includes free and paid SMS usage // This total is includes free and paid SMS usage
$authPhoneTotal = Authorization::skip(fn () => $dbForProject->sum('stats', 'value', [ $authPhoneTotal = $authorization->skip(fn () => $dbForProject->sum('stats', 'value', [
Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]), Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]),
Query::equal('period', ['1d']), Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $firstDay), Query::greaterThanEqual('time', $firstDay),
@@ -294,7 +295,7 @@ App::get('/v1/project/usage')
])); ]));
// This estimate is only for paid SMS usage // This estimate is only for paid SMS usage
$authPhoneMetrics = Authorization::skip(fn () => $dbForProject->find('stats', [ $authPhoneMetrics = $authorization->skip(fn () => $dbForProject->find('stats', [
Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'), Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'),
Query::equal('period', ['1d']), Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $firstDay), Query::greaterThanEqual('time', $firstDay),
+98 -98
View File
@@ -32,6 +32,7 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role; use Utopia\Database\Helpers\Role;
use Utopia\Database\Query; use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID; use Utopia\Database\Validator\UID;
@@ -433,20 +434,20 @@ App::post('/v1/storage/buckets/:bucketId/files')
->inject('mode') ->inject('mode')
->inject('deviceForFiles') ->inject('deviceForFiles')
->inject('deviceForLocal') ->inject('deviceForLocal')
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal) { ->inject('authorization')
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
$validator = new Authorization(Database::PERMISSION_CREATE); if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
throw new Exception(Exception::USER_UNAUTHORIZED);
} }
$allowedPermissions = [ $allowedPermissions = [
@@ -469,7 +470,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
} }
// Users can only manage their own roles, API keys and Admin users can manage any // Users can only manage their own roles, API keys and Admin users can manage any
$roles = Authorization::getRoles(); $roles = $authorization->getRoles();
if (!User::isApp($roles) && !User::isPrivileged($roles)) { if (!User::isApp($roles) && !User::isPrivileged($roles)) {
foreach (Database::PERMISSIONS as $type) { foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) { foreach ($permissions as $permission) {
@@ -482,7 +483,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
$permission->getIdentifier(), $permission->getIdentifier(),
$permission->getDimension() $permission->getDimension()
))->toString(); ))->toString();
if (!Authorization::isRole($role)) { if (!$authorization->hasRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
} }
} }
@@ -708,11 +709,10 @@ App::post('/v1/storage/buckets/:bucketId/files')
* However as with chunk upload even if we are updating, we are essentially creating a file * However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update * adding it's new chunk so we validate create permission instead of update
*/ */
$validator = new Authorization(Database::PERMISSION_CREATE); if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
throw new Exception(Exception::USER_UNAUTHORIZED);
} }
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file)); $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
} }
} else { } else {
if ($file->isEmpty()) { if ($file->isEmpty()) {
@@ -753,13 +753,12 @@ App::post('/v1/storage/buckets/:bucketId/files')
* However as with chunk upload even if we are updating, we are essentially creating a file * However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update * adding it's new chunk so we validate create permission instead of update
*/ */
$validator = new Authorization(Database::PERMISSION_CREATE); if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
throw new Exception(Exception::USER_UNAUTHORIZED);
} }
try { try {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file)); $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
} catch (NotFoundException) { } catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
@@ -803,22 +802,22 @@ App::get('/v1/storage/buckets/:bucketId/files')
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('mode') ->inject('mode')
->action(function (string $bucketId, array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject, string $mode) { ->action(function (string $bucketId, array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject, Authorization $authorization, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
$fileSecurity = $bucket->getAttribute('fileSecurity', false); $fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ); $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) { if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
$queries = Query::parseQueries($queries); $queries = Query::parseQueries($queries);
@@ -847,7 +846,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
if ($fileSecurity && !$valid) { if ($fileSecurity && !$valid) {
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId); $cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else { } else {
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
} }
if ($cursorDocument->isEmpty()) { if ($cursorDocument->isEmpty()) {
@@ -857,15 +856,13 @@ App::get('/v1/storage/buckets/:bucketId/files')
$cursor->setValue($cursorDocument); $cursor->setValue($cursorDocument);
} }
$filterQueries = Query::groupByType($queries)['filters'];
try { try {
if ($fileSecurity && !$valid) { if ($fileSecurity && !$valid) {
$files = $dbForProject->find('bucket_' . $bucket->getSequence(), $queries); $files = $dbForProject->find('bucket_' . $bucket->getSequence(), $queries);
$total = $includeTotal ? $dbForProject->count('bucket_' . $bucket->getSequence(), $filterQueries, APP_LIMIT_COUNT) : 0; $total = $includeTotal ? $dbForProject->count('bucket_' . $bucket->getSequence(), $queries, APP_LIMIT_COUNT) : 0;
} else { } else {
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getSequence(), $queries)); $files = $authorization->skip(fn () => $dbForProject->find('bucket_' . $bucket->getSequence(), $queries));
$total = $includeTotal ? Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getSequence(), $filterQueries, APP_LIMIT_COUNT)) : 0; $total = $includeTotal ? $authorization->skip(fn () => $dbForProject->count('bucket_' . $bucket->getSequence(), $queries, APP_LIMIT_COUNT)) : 0;
} }
} catch (NotFoundException) { } catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -904,28 +901,28 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->param('fileId', '', new UID(), 'File ID.') ->param('fileId', '', new UID(), 'File ID.')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('mode') ->inject('mode')
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode) { ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Authorization $authorization, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
$fileSecurity = $bucket->getAttribute('fileSecurity', false); $fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ); $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) { if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
if ($fileSecurity && !$valid) { if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId); $file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else { } else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
} }
if ($file->isEmpty()) { if ($file->isEmpty()) {
@@ -981,17 +978,18 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->inject('deviceForFiles') ->inject('deviceForFiles')
->inject('deviceForLocal') ->inject('deviceForLocal')
->inject('project') ->inject('project')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, ?string $token, Request $request, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal, Document $project) { ->inject('authorization')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, ?string $token, Request $request, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal, Document $project, Authorization $authorization) {
if (!\extension_loaded('imagick')) { if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing'); throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
} }
/* @type Document $bucket */ /* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1003,17 +1001,16 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence(); $isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$fileSecurity = $bucket->getAttribute('fileSecurity', false); $fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ); $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid && !$isToken) { if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
if ($fileSecurity && !$valid && !$isToken) { if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId); $file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else { } else {
/* @type Document $file */ /* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
} }
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) { if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
@@ -1135,11 +1132,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg']; $contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
//Do not update transformedAt if it's a console user //Do not update transformedAt if it's a console user
if (!User::isPrivileged(Authorization::getRoles())) { if (!User::isPrivileged($authorization->getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', ''); $transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) { if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now()); $file->setAttribute('transformedAt', DateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file)); $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
} }
} }
@@ -1179,15 +1176,16 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->inject('request') ->inject('request')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('mode') ->inject('mode')
->inject('resourceToken') ->inject('resourceToken')
->inject('deviceForFiles') ->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, ?string $token, Request $request, Response $response, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) { ->action(function (string $bucketId, string $fileId, ?string $token, Request $request, Response $response, Database $dbForProject, Authorization $authorization, string $mode, Document $resourceToken, Device $deviceForFiles) {
/* @type Document $bucket */ /* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1195,21 +1193,20 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence(); $isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$fileSecurity = $bucket->getAttribute('fileSecurity', false); $fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ); $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid && !$isToken) { if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
if ($fileSecurity && !$valid && !$isToken) { if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId); $file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else { } else {
/* @type Document $file */ /* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
} }
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) { if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
if ($file->isEmpty()) { if ($file->isEmpty()) {
@@ -1343,12 +1340,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->inject('mode') ->inject('mode')
->inject('resourceToken') ->inject('resourceToken')
->inject('deviceForFiles') ->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, ?string $token, Response $response, Request $request, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) { ->inject('authorization')
->action(function (string $bucketId, string $fileId, ?string $token, Response $response, Request $request, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles, Authorization $authorization) {
/* @type Document $bucket */ /* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1356,21 +1354,20 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence(); $isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$fileSecurity = $bucket->getAttribute('fileSecurity', false); $fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ); $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid && !$isToken) { if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
if ($fileSecurity && !$valid && !$isToken) { if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId); $file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else { } else {
/* @type Document $file */ /* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
} }
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) { if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
if ($file->isEmpty()) { if ($file->isEmpty()) {
@@ -1499,7 +1496,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
->inject('project') ->inject('project')
->inject('mode') ->inject('mode')
->inject('deviceForFiles') ->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Database $dbForPlatform, Document $project, string $mode, Device $deviceForFiles) { ->inject('authorization')
->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Database $dbForPlatform, Document $project, string $mode, Device $deviceForFiles, Authorization $authorization) {
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); $decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try { try {
@@ -1520,15 +1518,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
$disposition = $decoded['disposition'] ?? 'inline'; $disposition = $decoded['disposition'] ?? 'inline';
$dbForProject = $isInternal ? $dbForPlatform : $dbForProject; $dbForProject = $isInternal ? $dbForPlatform : $dbForProject;
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if ($file->isEmpty()) { if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
} }
@@ -1536,7 +1534,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
$mimes = Config::getParam('storage-mimes'); $mimes = Config::getParam('storage-mimes');
$path = $file->getAttribute('path', ''); $path = $file->getAttribute('path', '');
if (!$deviceForFiles->exists($path)) { if (!$deviceForFiles->exists($path)) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path); throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
} }
@@ -1673,26 +1670,26 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('user') ->inject('user')
->inject('mode') ->inject('mode')
->inject('queueForEvents') ->inject('queueForEvents')
->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents) { ->inject('authorization')
->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents, Authorization $authorization) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
$fileSecurity = $bucket->getAttribute('fileSecurity', false); $fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_UPDATE); $valid = $authorization->isValid(new Input(Database::PERMISSION_UPDATE, $bucket->getUpdate()));
$valid = $validator->isValid($bucket->getUpdate());
if (!$fileSecurity && !$valid) { if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
// Read permission should not be required for update // Read permission should not be required for update
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if ($file->isEmpty()) { if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
@@ -1706,7 +1703,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
]); ]);
// Users can only manage their own roles, API keys and Admin users can manage any // Users can only manage their own roles, API keys and Admin users can manage any
$roles = Authorization::getRoles(); $roles = $authorization->getRoles();
if (!User::isApp($roles) && !User::isPrivileged($roles) && !\is_null($permissions)) { if (!User::isApp($roles) && !User::isPrivileged($roles) && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) { foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) { foreach ($permissions as $permission) {
@@ -1719,7 +1716,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
$permission->getIdentifier(), $permission->getIdentifier(),
$permission->getDimension() $permission->getDimension()
))->toString(); ))->toString();
if (!Authorization::isRole($role)) { if (!$authorization->hasRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
} }
} }
@@ -1740,7 +1737,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
if ($fileSecurity && !$valid) { if ($fileSecurity && !$valid) {
$file = $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file); $file = $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file);
} else { } else {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file)); $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
} }
} catch (NotFoundException) { } catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1788,33 +1785,34 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('mode') ->inject('mode')
->inject('deviceForFiles') ->inject('deviceForFiles')
->inject('queueForDeletes') ->inject('queueForDeletes')
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes) { ->inject('authorization')
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes, Authorization $authorization) {
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = User::isApp(Authorization::getRoles()); $isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} }
$fileSecurity = $bucket->getAttribute('fileSecurity', false); $fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_DELETE); $valid = $authorization->isValid(new Input(Database::PERMISSION_DELETE, $bucket->getDelete()));
$valid = $validator->isValid($bucket->getDelete());
if (!$fileSecurity && !$valid) { if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
// Read permission should not be required for delete // Read permission should not be required for delete
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if ($file->isEmpty()) { if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
} }
// Make sure we don't delete the file before the document permission check occurs // Make sure we don't delete the file before the document permission check occurs
if ($fileSecurity && !$valid && !$validator->isValid($file->getDelete())) { $validFile = $authorization->isValid(new Input(Database::PERMISSION_DELETE, $file->getDelete()));
throw new Exception(Exception::USER_UNAUTHORIZED); if ($fileSecurity && !$valid && !$validFile) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
} }
$deviceDeleted = false; $deviceDeleted = false;
@@ -1838,7 +1836,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
if ($fileSecurity && !$valid) { if ($fileSecurity && !$valid) {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId); $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId);
} else { } else {
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId)); $deleted = $authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId));
} }
} catch (NotFoundException) { } catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1883,7 +1881,8 @@ App::get('/v1/storage/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->action(function (string $range, Response $response, Database $dbForProject) { ->inject('authorization')
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
$periods = Config::getParam('usage', []); $periods = Config::getParam('usage', []);
$stats = $usage = []; $stats = $usage = [];
@@ -1895,7 +1894,7 @@ App::get('/v1/storage/usage')
]; ];
$total = []; $total = [];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
foreach ($metrics as $metric) { foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [ $result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]), Query::equal('metric', [$metric]),
@@ -1973,7 +1972,8 @@ App::get('/v1/storage/:bucketId/usage')
->inject('project') ->inject('project')
->inject('dbForProject') ->inject('dbForProject')
->inject('getLogsDB') ->inject('getLogsDB')
->action(function (string $bucketId, string $range, Response $response, Document $project, Database $dbForProject, callable $getLogsDB) { ->inject('authorization')
->action(function (string $bucketId, string $range, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, Authorization $authorization) {
$dbForLogs = call_user_func($getLogsDB, $project); $dbForLogs = call_user_func($getLogsDB, $project);
$bucket = $dbForProject->getDocument('buckets', $bucketId); $bucket = $dbForProject->getDocument('buckets', $bucketId);
@@ -1991,7 +1991,7 @@ App::get('/v1/storage/:bucketId/usage')
str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED), str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED),
]; ];
Authorization::skip(function () use ($dbForProject, $dbForLogs, $bucket, $days, $metrics, &$stats) { $authorization->skip(function () use ($dbForProject, $dbForLogs, $bucket, $days, $metrics, &$stats) {
foreach ($metrics as $metric) { foreach ($metrics as $metric) {
$db = ($metric === str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED)) $db = ($metric === str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED))
? $dbForLogs ? $dbForLogs
+35 -28
View File
@@ -86,16 +86,17 @@ App::post('/v1/teams')
->inject('response') ->inject('response')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents') ->inject('queueForEvents')
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp(Authorization::getRoles()); $isAppUser = User::isApp($authorization->getRoles());
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId; $teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
try { try {
$team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([ $team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([
'$id' => $teamId, '$id' => $teamId,
'$permissions' => [ '$permissions' => [
Permission::read(Role::team($teamId)), Permission::read(Role::team($teamId)),
@@ -491,6 +492,7 @@ App::post('/v1/teams/:teamId/memberships')
->inject('project') ->inject('project')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('locale') ->inject('locale')
->inject('queueForMails') ->inject('queueForMails')
->inject('queueForMessaging') ->inject('queueForMessaging')
@@ -500,9 +502,9 @@ App::post('/v1/teams/:teamId/memberships')
->inject('plan') ->inject('plan')
->inject('proofForPassword') ->inject('proofForPassword')
->inject('proofForToken') ->inject('proofForToken')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) { ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
$isAppUser = User::isApp(Authorization::getRoles()); $isAppUser = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$url = htmlentities($url); $url = htmlentities($url);
if (empty($url)) { if (empty($url)) {
@@ -619,13 +621,13 @@ App::post('/v1/teams/:teamId/memberships')
]); ]);
try { try {
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', $userDocument)); $invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', $userDocument));
} catch (Duplicate $th) { } catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS); throw new Exception(Exception::USER_ALREADY_EXISTS);
} }
} }
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner'); $isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team'); throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
@@ -661,11 +663,11 @@ App::post('/v1/teams/:teamId/memberships')
]); ]);
$membership = ($isPrivilegedUser || $isAppUser) ? $membership = ($isPrivilegedUser || $isAppUser) ?
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) : $authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
$dbForProject->createDocument('memberships', $membership); $dbForProject->createDocument('memberships', $membership);
if ($isPrivilegedUser || $isAppUser) { if ($isPrivilegedUser || $isAppUser) {
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
} }
} elseif ($membership->getAttribute('confirm') === false) { } elseif ($membership->getAttribute('confirm') === false) {
$membership->setAttribute('secret', $proofForToken->hash($secret)); $membership->setAttribute('secret', $proofForToken->hash($secret));
@@ -677,7 +679,7 @@ App::post('/v1/teams/:teamId/memberships')
} }
$membership = ($isPrivilegedUser || $isAppUser) ? $membership = ($isPrivilegedUser || $isAppUser) ?
Authorization::skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) : $authorization->skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
$dbForProject->updateDocument('memberships', $membership->getId(), $membership); $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
} else { } else {
throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED); throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED);
@@ -863,7 +865,8 @@ App::get('/v1/teams/:teamId/memberships')
->inject('response') ->inject('response')
->inject('project') ->inject('project')
->inject('dbForProject') ->inject('dbForProject')
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject) { ->inject('authorization')
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
$team = $dbForProject->getDocument('teams', $teamId); $team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) { if ($team->isEmpty()) {
@@ -933,7 +936,7 @@ App::get('/v1/teams/:teamId/memberships')
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true, 'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
]; ];
$roles = Authorization::getRoles(); $roles = $authorization->getRoles();
$isPrivilegedUser = User::isPrivileged($roles); $isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles); $isAppUser = User::isApp($roles);
@@ -1004,7 +1007,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->inject('response') ->inject('response')
->inject('project') ->inject('project')
->inject('dbForProject') ->inject('dbForProject')
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) { ->inject('authorization')
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
$team = $dbForProject->getDocument('teams', $teamId); $team = $dbForProject->getDocument('teams', $teamId);
@@ -1024,7 +1028,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true, 'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
]; ];
$roles = Authorization::getRoles(); $roles = $authorization->getRoles();
$isPrivilegedUser = User::isPrivileged($roles); $isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles); $isAppUser = User::isApp($roles);
@@ -1103,8 +1107,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->inject('user') ->inject('user')
->inject('project') ->inject('project')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents') ->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) { ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
$team = $dbForProject->getDocument('teams', $teamId); $team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) { if ($team->isEmpty()) {
@@ -1121,9 +1126,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::USER_NOT_FOUND); throw new Exception(Exception::USER_NOT_FOUND);
} }
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp(Authorization::getRoles()); $isAppUser = User::isApp($authorization->getRoles());
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner'); $isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
if ($project->getId() === 'console') { if ($project->getId() === 'console') {
// Quick check: fetch up to 2 owners to determine if only one exists // Quick check: fetch up to 2 owners to determine if only one exists
@@ -1204,12 +1209,13 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('response') ->inject('response')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('project') ->inject('project')
->inject('geodb') ->inject('geodb')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('store') ->inject('store')
->inject('proofForToken') ->inject('proofForToken')
->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) { ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
$protocol = $request->getProtocol(); $protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId); $membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1218,7 +1224,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND); throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
} }
$team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId)); $team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId));
if ($team->isEmpty()) { if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND); throw new Exception(Exception::TEAM_NOT_FOUND);
@@ -1254,11 +1260,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->setAttribute('confirm', true) ->setAttribute('confirm', true)
; ;
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true))); $authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
// Create session for the user if not logged in // Create session for the user if not logged in
if (!$hasSession) { if (!$hasSession) {
Authorization::setRole(Role::user($user->getId())->toString()); $authorization->addRole(Role::user($user->getId())->toString());
$detector = new Detector($request->getUserAgent('UNKNOWN')); $detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP()); $record = $geodb->get($request->getIP());
@@ -1286,7 +1292,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$session = $dbForProject->createDocument('sessions', $session); $session = $dbForProject->createDocument('sessions', $session);
Authorization::setRole(Role::user($userId)->toString()); $authorization->addRole(Role::user($userId)->toString());
$encoded = $store $encoded = $store
->setProperty('id', $user->getId()) ->setProperty('id', $user->getId())
@@ -1324,7 +1330,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$dbForProject->purgeCachedDocument('users', $user->getId()); $dbForProject->purgeCachedDocument('users', $user->getId());
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$queueForEvents $queueForEvents
->setParam('userId', $user->getId()) ->setParam('userId', $user->getId())
@@ -1368,8 +1374,9 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
->inject('project') ->inject('project')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents') ->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) { ->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
$membership = $dbForProject->getDocument('memberships', $membershipId); $membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1427,7 +1434,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$dbForProject->purgeCachedDocument('users', $profile->getId()); $dbForProject->purgeCachedDocument('users', $profile->getId());
if ($membership->getAttribute('confirm')) { // Count only confirmed members if ($membership->getAttribute('confirm')) { // Count only confirmed members
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0)); $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
} }
$queueForEvents $queueForEvents
+3 -3
View File
@@ -2674,8 +2674,8 @@ App::get('/v1/users/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('register') ->inject('authorization')
->action(function (string $range, Response $response, Database $dbForProject) { ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
$periods = Config::getParam('usage', []); $periods = Config::getParam('usage', []);
$stats = $usage = []; $stats = $usage = [];
@@ -2685,7 +2685,7 @@ App::get('/v1/users/usage')
METRIC_SESSIONS, METRIC_SESSIONS,
]; ];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $count => $metric) { foreach ($metrics as $count => $metric) {
$result = $dbForProject->findOne('stats', [ $result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]), Query::equal('metric', [$metric]),
+31 -29
View File
@@ -76,7 +76,7 @@ use Utopia\VCS\Exception\RepositoryNotFound;
use function Swoole\Coroutine\batch; 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, array $platform) { $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, Authorization $authorization, Build $queueForBuilds, callable $getProjectDB, Request $request, array $platform) {
$errors = []; $errors = [];
foreach ($repositories as $repository) { foreach ($repositories as $repository) {
try { try {
@@ -87,12 +87,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
} }
$projectId = $repository->getAttribute('projectId'); $projectId = $repository->getAttribute('projectId');
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project); $dbForProject = $getProjectDB($project);
$resourceCollection = $resourceType === "function" ? 'functions' : 'sites'; $resourceCollection = $resourceType === "function" ? 'functions' : 'sites';
$resourceId = $repository->getAttribute('resourceId'); $resourceId = $repository->getAttribute('resourceId');
$resource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId)); $resource = $authorization->skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
$resourceInternalId = $resource->getSequence(); $resourceInternalId = $resource->getSequence();
$deploymentId = ID::unique(); $deploymentId = ID::unique();
@@ -141,7 +141,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = ''; $latestCommentId = '';
if (!empty($providerPullRequestId) && $resource->getAttribute('providerSilentMode', false) === false) { if (!empty($providerPullRequestId) && $resource->getAttribute('providerSilentMode', false) === false) {
$latestComment = Authorization::skip(fn () => $dbForPlatform->findOne('vcsComments', [ $latestComment = $authorization->skip(fn () => $dbForPlatform->findOne('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerPullRequestId', [$providerPullRequestId]), Query::equal('providerPullRequestId', [$providerPullRequestId]),
Query::orderDesc('$createdAt'), Query::orderDesc('$createdAt'),
@@ -180,7 +180,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
} finally { } finally {
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId)); $authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
} }
} }
} else { } else {
@@ -191,7 +191,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if (!empty($latestCommentId)) { if (!empty($latestCommentId)) {
$teamId = $project->getAttribute('teamId', ''); $teamId = $project->getAttribute('teamId', '');
$latestComment = Authorization::skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([ $latestComment = $authorization->skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([
'$id' => ID::unique(), '$id' => ID::unique(),
'$permissions' => [ '$permissions' => [
Permission::read(Role::team(ID::custom($teamId))), Permission::read(Role::team(ID::custom($teamId))),
@@ -212,7 +212,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
} }
} }
} elseif (!empty($providerBranch)) { } elseif (!empty($providerBranch)) {
$latestComments = Authorization::skip(fn () => $dbForPlatform->find('vcsComments', [ $latestComments = $authorization->skip(fn () => $dbForPlatform->find('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerBranch', [$providerBranch]), Query::equal('providerBranch', [$providerBranch]),
Query::orderDesc('$createdAt'), Query::orderDesc('$createdAt'),
@@ -251,7 +251,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
} finally { } finally {
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId)); $authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
} }
} }
} }
@@ -294,7 +294,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$commands[] = $resource->getAttribute('commands', ''); $commands[] = $resource->getAttribute('commands', '');
} }
$deployment = Authorization::skip(fn () => $dbForProject->createDocument('deployments', new Document([ $deployment = $authorization->skip(fn () => $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId, '$id' => $deploymentId,
'$permissions' => [ '$permissions' => [
Permission::read(Role::any()), Permission::read(Role::any()),
@@ -334,7 +334,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
->setAttribute('latestDeploymentInternalId', $deployment->getSequence()) ->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt()) ->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', '')); ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
Authorization::skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource)); $authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
if ($resource->getCollection() === 'sites') { if ($resource->getCollection() === 'sites') {
$projectId = $project->getId(); $projectId = $project->getId();
@@ -344,7 +344,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$domain = ID::unique() . "." . $sitesDomain; $domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain); $ruleId = md5($domain);
$previewRuleId = $ruleId; $previewRuleId = $ruleId;
Authorization::skip( $authorization->skip(
fn () => $dbForPlatform->createDocument('rules', new Document([ fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId, '$id' => $ruleId,
'projectId' => $project->getId(), 'projectId' => $project->getId(),
@@ -377,7 +377,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}"; $domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
$ruleId = md5($domain); $ruleId = md5($domain);
try { try {
Authorization::skip( $authorization->skip(
fn () => $dbForPlatform->createDocument('rules', new Document([ fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId, '$id' => $ruleId,
'projectId' => $project->getId(), 'projectId' => $project->getId(),
@@ -408,7 +408,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$domain = "commit-" . substr($providerCommitHash, 0, 16) . ".{$sitesDomain}"; $domain = "commit-" . substr($providerCommitHash, 0, 16) . ".{$sitesDomain}";
$ruleId = md5($domain); $ruleId = md5($domain);
try { try {
Authorization::skip( $authorization->skip(
fn () => $dbForPlatform->createDocument('rules', new Document([ fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId, '$id' => $ruleId,
'projectId' => $project->getId(), 'projectId' => $project->getId(),
@@ -460,7 +460,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if ($lockAcquired) { if ($lockAcquired) {
// Wrap in try/finally to ensure lock file gets deleted // Wrap in try/finally to ensure lock file gets deleted
try { try {
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId)); $rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : ''; $previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
@@ -472,7 +472,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()); $github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment());
} }
} finally { } finally {
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId)); $authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
} }
} }
} }
@@ -1476,11 +1476,12 @@ App::post('/v1/vcs/github/events')
->inject('request') ->inject('request')
->inject('response') ->inject('response')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('authorization')
->inject('getProjectDB') ->inject('getProjectDB')
->inject('queueForBuilds') ->inject('queueForBuilds')
->inject('platform') ->inject('platform')
->action( ->action(
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) { function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
$payload = $request->getRawPayload(); $payload = $request->getRawPayload();
$signatureRemote = $request->getHeader('x-hub-signature-256', ''); $signatureRemote = $request->getHeader('x-hub-signature-256', '');
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', ''); $signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
@@ -1516,14 +1517,14 @@ App::post('/v1/vcs/github/events')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
//find resourceId from relevant resources table //find resourceId from relevant resources table
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [ $repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::limit(100), Query::limit(100),
])); ]));
// create new deployment only on push (not committed by us) and not when branch is created or deleted // 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) { 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, $platform); $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
} }
} elseif ($event == $github::EVENT_INSTALLATION) { } elseif ($event == $github::EVENT_INSTALLATION) {
if ($parsedPayload["action"] == "deleted") { if ($parsedPayload["action"] == "deleted") {
@@ -1536,16 +1537,16 @@ App::post('/v1/vcs/github/events')
]); ]);
foreach ($installations as $installation) { foreach ($installations as $installation) {
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [ $repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('installationInternalId', [$installation->getSequence()]), Query::equal('installationInternalId', [$installation->getSequence()]),
Query::limit(1000) Query::limit(1000)
])); ]));
foreach ($repositories as $repository) { foreach ($repositories as $repository) {
Authorization::skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId())); $authorization->skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
} }
Authorization::skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId())); $authorization->skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId()));
} }
} }
} elseif ($event == $github::EVENT_PULL_REQUEST) { } elseif ($event == $github::EVENT_PULL_REQUEST) {
@@ -1574,12 +1575,12 @@ App::post('/v1/vcs/github/events')
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? ''; $providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitMessage = $commitDetails["commitMessage"] ?? ''; $providerCommitMessage = $commitDetails["commitMessage"] ?? '';
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [ $repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt') Query::orderDesc('$createdAt')
])); ]));
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform); $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
} elseif ($parsedPayload["action"] == "closed") { } elseif ($parsedPayload["action"] == "closed") {
// Allowed external contributions cleanup // Allowed external contributions cleanup
@@ -1588,7 +1589,7 @@ App::post('/v1/vcs/github/events')
$external = $parsedPayload["external"] ?? true; $external = $parsedPayload["external"] ?? true;
if ($external) { if ($external) {
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [ $repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt') Query::orderDesc('$createdAt')
])); ]));
@@ -1599,7 +1600,7 @@ App::post('/v1/vcs/github/events')
if (\in_array($providerPullRequestId, $providerPullRequestIds)) { if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]); $providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds); $repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository)); $repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
} }
} }
} }
@@ -1786,17 +1787,18 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
->inject('response') ->inject('response')
->inject('project') ->inject('project')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('authorization')
->inject('getProjectDB') ->inject('getProjectDB')
->inject('queueForBuilds') ->inject('queueForBuilds')
->inject('platform') ->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) { ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
$installation = $dbForPlatform->getDocument('installations', $installationId); $installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) { if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND); throw new Exception(Exception::INSTALLATION_NOT_FOUND);
} }
$repository = Authorization::skip(fn () => $dbForPlatform->findOne('repositories', [ $repository = $authorization->skip(fn () => $dbForPlatform->findOne('repositories', [
Query::equal('$id', [$repositoryId]), Query::equal('$id', [$repositoryId]),
Query::equal('projectInternalId', [$project->getSequence()]) Query::equal('projectInternalId', [$project->getSequence()])
])); ]));
@@ -1814,7 +1816,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
// TODO: Delete from array when PR is closed // TODO: Delete from array when PR is closed
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository)); $repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); $privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
@@ -1846,7 +1848,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
$providerCommitMessage = $pullRequestResponse['title'] ?? ''; $providerCommitMessage = $pullRequestResponse['title'] ?? '';
$providerCommitUrl = $pullRequestResponse['html_url'] ?? ''; $providerCommitUrl = $pullRequestResponse['html_url'] ?? '';
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform); $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, '', '', '', '', $providerCommitHash, '', '', '', '', $providerPullRequestId, true, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
$response->noContent(); $response->noContent();
}); });
+40 -34
View File
@@ -59,7 +59,7 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); 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) 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, Authorization $authorization, ?Key $apiKey)
{ {
$host = $request->getHostname() ?? ''; $host = $request->getHostname() ?? '';
if (!empty($previewHostname)) { if (!empty($previewHostname)) {
@@ -67,16 +67,16 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
} }
// TODO: (@Meldiron) Remove after 1.7.x migration // TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = Authorization::skip(function () use ($dbForPlatform, $host, $isMd5) { $rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
if ($isMd5) { } else {
return $dbForPlatform->getDocument('rules', md5($host)); $rule = $authorization->skip(
} fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$host]),
return $dbForPlatform->findOne('rules', [ Query::limit(1)
Query::equal('domain', [$host]), ])
]) ?? new Document(); )[0] ?? new Document();
}); }
$errorView = __DIR__ . '/../views/general/error.phtml'; $errorView = __DIR__ . '/../views/general/error.phtml';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
@@ -111,7 +111,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
} }
$projectId = $rule->getAttribute('projectId'); $projectId = $rule->getAttribute('projectId');
$project = Authorization::skip( $project = $authorization->skip(
fn () => $dbForPlatform->getDocument('projects', $projectId) fn () => $dbForPlatform->getDocument('projects', $projectId)
); );
@@ -119,7 +119,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$accessedAt = $project->getAttribute('accessedAt', 0); $accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now()); $project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project)); $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
} }
/** /**
@@ -158,7 +158,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
/** @var Document $deployment */ /** @var Document $deployment */
if (!empty($rule->getAttribute('deploymentId', ''))) { if (!empty($rule->getAttribute('deploymentId', ''))) {
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId'))); $deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
} else { } else {
// 1.6.x DB schema compatibility // 1.6.x DB schema compatibility
// TODO: Make sure deploymentId is never empty, and remove this code // TODO: Make sure deploymentId is never empty, and remove this code
@@ -172,15 +172,15 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
// Document of site or function // Document of site or function
$resource = $resourceType === 'function' ? $resource = $resourceType === 'function' ?
Authorization::skip(fn () => $dbForProject->getDocument('functions', $resourceId)) : $authorization->skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
Authorization::skip(fn () => $dbForProject->getDocument('sites', $resourceId)); $authorization->skip(fn () => $dbForProject->getDocument('sites', $resourceId));
// ID of active deployments // ID of active deployments
// Attempts to use attribute from both schemas (1.6 and 1.7) // Attempts to use attribute from both schemas (1.6 and 1.7)
$activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', '')); $activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', ''));
// Get deployment document, as intended originally // Get deployment document, as intended originally
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId)); $deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
} }
if ($deployment->getAttribute('resourceType', '') === 'functions') { if ($deployment->getAttribute('resourceType', '') === 'functions') {
@@ -199,8 +199,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
} }
$resource = $type === 'function' ? $resource = $type === 'function' ?
Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) : $authorization->skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
Authorization::skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', ''))); $authorization->skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
$isPreview = $type === 'function' ? false : ($rule->getAttribute('trigger', '') !== 'manual'); $isPreview = $type === 'function' ? false : ($rule->getAttribute('trigger', '') !== 'manual');
@@ -242,7 +242,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$userExists = false; $userExists = false;
$userId = $payload['userId'] ?? ''; $userId = $payload['userId'] ?? '';
if (!empty($userId)) { if (!empty($userId)) {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); $user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
if (!$user->isEmpty() && $user->getAttribute('status', false)) { if (!$user->isEmpty() && $user->getAttribute('status', false)) {
$userExists = true; $userExists = true;
} }
@@ -255,7 +255,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
} }
$membershipExists = false; $membershipExists = false;
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
if (!$project->isEmpty() && isset($user)) { if (!$project->isEmpty() && isset($user)) {
$teamId = $project->getAttribute('teamId', ''); $teamId = $project->getAttribute('teamId', '');
$membership = $user->find('teamId', $teamId, 'memberships'); $membership = $user->find('teamId', $teamId, 'memberships');
@@ -862,15 +862,16 @@ App::init()
->inject('devKey') ->inject('devKey')
->inject('apiKey') ->inject('apiKey')
->inject('cors') ->inject('cors')
->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) { ->inject('authorization')
->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, Authorization $authorization) {
/* /*
* Appwrite Router * Appwrite Router
*/ */
$hostname = $request->getHostname() ?? ''; $hostname = $request->getHostname() ?? '';
$platformHostnames = $platform['hostnames'] ?? []; $platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain // Only run Router when external domain
if (!in_array($hostname, $platformHostnames) || !empty($previewHostname)) { 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)) { if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
$utopia->getRoute()?->label('router', true); $utopia->getRoute()?->label('router', true);
} }
} }
@@ -1144,7 +1145,8 @@ App::options()
->inject('devKey') ->inject('devKey')
->inject('apiKey') ->inject('apiKey')
->inject('cors') ->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, Cors $cors) { ->inject('authorization')
->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, Authorization $authorization) {
/* /*
* Appwrite Router * Appwrite Router
*/ */
@@ -1185,7 +1187,8 @@ App::error()
->inject('log') ->inject('log')
->inject('queueForStatsUsage') ->inject('queueForStatsUsage')
->inject('devKey') ->inject('devKey')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) { ->inject('authorization')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, Document $devKey, Authorization $authorization) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN'); $version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->getRoute(); $route = $utopia->getRoute();
$class = \get_class($error); $class = \get_class($error);
@@ -1267,7 +1270,7 @@ App::error()
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php * If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
*/ */
if (!$publish && $project->getId() !== 'console') { if (!$publish && $project->getId() !== 'console') {
if (!DBUser::isPrivileged(Authorization::getRoles())) { if (!DBUser::isPrivileged($authorization->getRoles())) {
$fileSize = 0; $fileSize = 0;
$file = $request->getFiles('file'); $file = $request->getFiles('file');
if (!empty($file)) { if (!empty($file)) {
@@ -1329,7 +1332,7 @@ App::error()
$log->addExtra('file', $error->getFile()); $log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine()); $log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString()); $log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles()); $log->addExtra('roles', $authorization->getRoles());
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD'; $action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
if (!empty($sdk)) { if (!empty($sdk)) {
@@ -1453,13 +1456,14 @@ App::get('/robots.txt')
->inject('platform') ->inject('platform')
->inject('previewHostname') ->inject('previewHostname')
->inject('apiKey') ->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, array $platform, string $previewHostname, ?Key $apiKey) { ->inject('authorization')
->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, Authorization $authorization) {
$platformHostnames = $platform['hostnames'] ?? []; $platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) { if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/robots.phtml'); $template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false)); $response->text($template->render(false));
} else { } else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) { if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
$utopia->getRoute()?->label('router', true); $utopia->getRoute()?->label('router', true);
} }
} }
@@ -1485,13 +1489,14 @@ App::get('/humans.txt')
->inject('platform') ->inject('platform')
->inject('previewHostname') ->inject('previewHostname')
->inject('apiKey') ->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, array $platform, string $previewHostname, ?Key $apiKey) { ->inject('authorization')
->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, Authorization $authorization) {
$platformHostnames = $platform['hostnames'] ?? []; $platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) { if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/humans.phtml'); $template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false)); $response->text($template->render(false));
} else { } else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) { if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
$utopia->getRoute()?->label('router', true); $utopia->getRoute()?->label('router', true);
} }
} }
@@ -1575,7 +1580,8 @@ App::get('/v1/ping')
->inject('project') ->inject('project')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('queueForEvents') ->inject('queueForEvents')
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) { ->inject('authorization')
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') { if ($project->isEmpty() || $project->getId() === 'console') {
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
} }
@@ -1587,7 +1593,7 @@ App::get('/v1/ping')
->setAttribute('pingCount', $pingCount) ->setAttribute('pingCount', $pingCount)
->setAttribute('pingedAt', $pingedAt); ->setAttribute('pingedAt', $pingedAt);
Authorization::skip(function () use ($dbForPlatform, $project) { $authorization->skip(function () use ($dbForPlatform, $project) {
$dbForPlatform->updateDocument('projects', $project->getId(), $project); $dbForPlatform->updateDocument('projects', $project->getId(), $project);
}); });
+28 -25
View File
@@ -30,6 +30,7 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document; use Utopia\Database\Document;
use Utopia\Database\Helpers\Role; use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Queue\Publisher; use Utopia\Queue\Publisher;
use Utopia\System\System; use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry; use Utopia\Telemetry\Adapter as Telemetry;
@@ -233,7 +234,8 @@ App::init()
->inject('mode') ->inject('mode')
->inject('team') ->inject('team')
->inject('apiKey') ->inject('apiKey')
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) { ->inject('authorization')
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
$route = $utopia->getRoute(); $route = $utopia->getRoute();
/** /**
@@ -318,7 +320,7 @@ App::init()
// Handle special app role case // Handle special app role case
if ($apiKey->getRole() === User::ROLE_APPS) { if ($apiKey->getRole() === User::ROLE_APPS) {
// Disable authorization checks for API keys // Disable authorization checks for API keys
Authorization::setDefaultStatus(false); $authorization->setDefaultStatus(false);
$user = new User([ $user = new User([
'$id' => '', '$id' => '',
@@ -392,14 +394,14 @@ App::init()
$scopes = \array_merge($scopes, $roles[$role]['scopes']); $scopes = \array_merge($scopes, $roles[$role]['scopes']);
} }
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
} }
$scopes = \array_unique($scopes); $scopes = \array_unique($scopes);
Authorization::setRole($role); $authorization->addRole($role);
foreach ($user->getRoles() as $authRole) { foreach ($user->getRoles($authorization) as $authRole) {
Authorization::setRole($authRole); $authorization->addRole($authRole);
} }
// Step 6: Update project and user last activity // Step 6: Update project and user last activity
@@ -407,7 +409,7 @@ App::init()
$accessedAt = $project->getAttribute('accessedAt', 0); $accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now()); $project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project)); $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
} }
} }
@@ -442,7 +444,7 @@ App::init()
if ( if (
array_key_exists($namespace, $project->getAttribute('services', [])) array_key_exists($namespace, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$namespace] && !$project->getAttribute('services', [])[$namespace]
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles())) && !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) { ) {
throw new Exception(Exception::GENERAL_SERVICE_DISABLED); throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
} }
@@ -509,14 +511,15 @@ App::init()
->inject('devKey') ->inject('devKey')
->inject('telemetry') ->inject('telemetry')
->inject('platform') ->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) { ->inject('authorization')
->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, Authorization $authorization) use ($usageDatabaseListener, $eventDatabaseListener) {
$route = $utopia->getRoute(); $route = $utopia->getRoute();
if ( if (
array_key_exists('rest', $project->getAttribute('apis', [])) array_key_exists('rest', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['rest'] && !$project->getAttribute('apis', [])['rest']
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles())) && !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) { ) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
} }
@@ -546,7 +549,7 @@ App::init()
$closestLimit = null; $closestLimit = null;
$roles = Authorization::getRoles(); $roles = $authorization->getRoles();
$isPrivilegedUser = User::isPrivileged($roles); $isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles); $isAppUser = User::isApp($roles);
@@ -657,10 +660,10 @@ App::init()
if ($useCache) { if ($useCache) {
$route = $utopia->match($request); $route = $utopia->match($request);
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview'; $isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !User::isPrivileged(Authorization::getRoles()); $isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !User::isPrivileged($authorization->getRoles());
$key = $request->cacheIdentifier(); $key = $request->cacheIdentifier();
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache( $cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
); );
@@ -677,10 +680,10 @@ App::init()
if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) { if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
$bucketId = $parts[1] ?? null; $bucketId = $parts[1] ?? null;
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence(); $isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) { if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -691,8 +694,7 @@ App::init()
} }
$fileSecurity = $bucket->getAttribute('fileSecurity', false); $fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ); $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid && !$isToken) { if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED);
} }
@@ -703,7 +705,7 @@ App::init()
if ($fileSecurity && !$valid && !$isToken) { if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId); $file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else { } else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId)); $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
} }
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) { if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
@@ -714,11 +716,11 @@ App::init()
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
} }
//Do not update transformedAt if it's a console user //Do not update transformedAt if it's a console user
if (!User::isPrivileged(Authorization::getRoles())) { if (!User::isPrivileged($authorization->getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', ''); $transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) { if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now()); $file->setAttribute('transformedAt', DateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file)); $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
} }
} }
} }
@@ -814,7 +816,8 @@ App::shutdown()
->inject('queueForWebhooks') ->inject('queueForWebhooks')
->inject('queueForRealtime') ->inject('queueForRealtime')
->inject('dbForProject') ->inject('dbForProject')
->action(function (App $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) { ->inject('authorization')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization) use ($parseLabel) {
$responsePayload = $response->getPayload(); $responsePayload = $response->getPayload();
@@ -940,11 +943,11 @@ App::shutdown()
$key = $request->cacheIdentifier(); $key = $request->cacheIdentifier();
$signature = md5($data['payload']); $signature = md5($data['payload']);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', 0); $accessedAt = $cacheLog->getAttribute('accessedAt', 0);
$now = DateTime::now(); $now = DateTime::now();
if ($cacheLog->isEmpty()) { if ($cacheLog->isEmpty()) {
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([ $authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
'$id' => $key, '$id' => $key,
'resource' => $resource, 'resource' => $resource,
'resourceType' => $resourceType, 'resourceType' => $resourceType,
@@ -954,7 +957,7 @@ App::shutdown()
]))); ])));
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) { } elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
$cacheLog->setAttribute('accessedAt', $now); $cacheLog->setAttribute('accessedAt', $now);
Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog)); $authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
// Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load() // Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load()
$cache->save($key, $data['payload']); $cache->save($key, $data['payload']);
} }
@@ -966,7 +969,7 @@ App::shutdown()
} }
if ($project->getId() !== 'console') { if ($project->getId() !== 'console') {
if (!User::isPrivileged(Authorization::getRoles())) { if (!User::isPrivileged($authorization->getRoles())) {
$fileSize = 0; $fileSize = 0;
$file = $request->getFiles('file'); $file = $request->getFiles('file');
if (!empty($file)) { if (!empty($file)) {
+4 -3
View File
@@ -36,7 +36,8 @@ App::init()
->inject('request') ->inject('request')
->inject('project') ->inject('project')
->inject('geodb') ->inject('geodb')
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) { ->inject('authorization')
->action(function (App $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', ''); $denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
if (!empty($denylist && $project->getId() === 'console')) { if (!empty($denylist && $project->getId() === 'console')) {
$countries = explode(',', $denylist); $countries = explode(',', $denylist);
@@ -49,8 +50,8 @@ App::init()
$route = $utopia->match($request); $route = $utopia->match($request);
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp(Authorization::getRoles()); $isAppUser = User::isApp($authorization->getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return; return;
+17 -11
View File
@@ -25,7 +25,6 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role; use Utopia\Database\Helpers\Role;
use Utopia\Database\Query; use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Logger\Log; use Utopia\Logger\Log;
use Utopia\Logger\Log\User; use Utopia\Logger\Log\User;
use Utopia\Pools\Group; use Utopia\Pools\Group;
@@ -259,7 +258,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools); createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
// create appwrite database, `dbForPlatform` is a direct access call. // create appwrite database, `dbForPlatform` is a direct access call.
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections) { createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
$authorization = $app->getResource('authorization');
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) { if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForPlatform); $audit = new Audit($dbForPlatform);
$audit->setup(); $audit->setup();
@@ -318,9 +319,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
$dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes); $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes);
} }
if (Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) { if ($authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
Console::info(" └── Creating screenshots bucket..."); Console::info(" └── Creating screenshots bucket...");
Authorization::skip(fn () => $dbForPlatform->createDocument('buckets', new Document([ $authorization->skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
'$id' => ID::custom('screenshots'), '$id' => ID::custom('screenshots'),
'$collection' => ID::custom('buckets'), '$collection' => ID::custom('buckets'),
'name' => 'Screenshots', 'name' => 'Screenshots',
@@ -335,7 +336,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'search' => 'buckets Screenshots', 'search' => 'buckets Screenshots',
]))); ])));
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')); $bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
Console::info(" └── Creating files collection for screenshots bucket..."); Console::info(" └── Creating files collection for screenshots bucket...");
$files = $collections['buckets']['files'] ?? []; $files = $collections['buckets']['files'] ?? [];
@@ -363,7 +364,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'orders' => $index['orders'], 'orders' => $index['orders'],
]), $files['indexes']); ]), $files['indexes']);
Authorization::skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes)); $authorization->skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
} }
}); });
@@ -454,8 +455,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
App::setResource('pools', fn () => $pools); App::setResource('pools', fn () => $pools);
try { try {
Authorization::cleanRoles(); $authorization = $app->getResource('authorization');
Authorization::setRole(Role::any()->toString());
$request->setAuthorization($authorization);
$response->setAuthorization($authorization);
$authorization->cleanRoles();
$authorization->addRole(Role::any()->toString());
$app->run($request, $response); $app->run($request, $response);
} catch (\Throwable $th) { } catch (\Throwable $th) {
@@ -497,7 +502,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
$log->addExtra('file', $th->getFile()); $log->addExtra('file', $th->getFile());
$log->addExtra('line', $th->getLine()); $log->addExtra('line', $th->getLine());
$log->addExtra('trace', $th->getTraceAsString()); $log->addExtra('trace', $th->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles()); $log->addExtra('roles', isset($authorization) ? $authorization->getRoles() : []);
$sdk = $route->getLabel("sdk", false); $sdk = $route->getLabel("sdk", false);
@@ -556,7 +561,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
/** @var Utopia\Database\Database $dbForPlatform */ /** @var Utopia\Database\Database $dbForPlatform */
$dbForPlatform = $app->getResource('dbForPlatform'); $dbForPlatform = $app->getResource('dbForPlatform');
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate) { Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) {
try { try {
$time = DateTime::now(); $time = DateTime::now();
$limit = 1000; $limit = 1000;
@@ -573,7 +578,8 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
} }
$results = []; $results = [];
try { try {
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries)); $authorization = $app->getResource('authorization');
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
} catch (Throwable $th) { } catch (Throwable $th) {
Console::error($th->getMessage()); Console::error($th->getMessage());
} }
+1
View File
@@ -60,6 +60,7 @@ const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds
const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes
const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes
const APP_DATABASE_QUERY_MAX_VALUES = 500; const APP_DATABASE_QUERY_MAX_VALUES = 500;
const APP_DATABASE_QUERY_MAX_VALUES_WORKER = 5000;
const APP_DATABASE_ENCRYPT_SIZE_MIN = 150; const APP_DATABASE_ENCRYPT_SIZE_MIN = 150;
const APP_DATABASE_TXN_TTL_MIN = 60; // 1 minute const APP_DATABASE_TXN_TTL_MIN = 60; // 1 minute
const APP_DATABASE_TXN_TTL_MAX = 3600; // 1 hour const APP_DATABASE_TXN_TTL_MAX = 3600; // 1 hour
+23 -24
View File
@@ -4,7 +4,6 @@ use Appwrite\OpenSSL\OpenSSL;
use Utopia\Database\Database; use Utopia\Database\Database;
use Utopia\Database\Document; use Utopia\Database\Document;
use Utopia\Database\Query; use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\System\System; use Utopia\System\System;
Database::addFilter( Database::addFilter(
@@ -70,11 +69,11 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
$attributes = $database->find('attributes', [ $attributes = $database->getAuthorization()->skip(fn () => $database->find('attributes', [
Query::equal('collectionInternalId', [$document->getSequence()]), Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForAttributes()), Query::limit($database->getLimitForAttributes()),
]); ]));
foreach ($attributes as $attribute) { foreach ($attributes as $attribute) {
$attributeType = $attribute->getAttribute('type'); $attributeType = $attribute->getAttribute('type');
@@ -105,12 +104,12 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return $database return $database->getAuthorization()->skip(fn () => $database
->find('indexes', [ ->find('indexes', [
Query::equal('collectionInternalId', [$document->getSequence()]), Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForIndexes()), Query::limit($database->getLimitForIndexes()),
]); ]));
} }
); );
@@ -120,11 +119,11 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return $database return $database->getAuthorization()->skip(fn () => $database
->find('platforms', [ ->find('platforms', [
Query::equal('projectInternalId', [$document->getSequence()]), Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
]); ]));
} }
); );
@@ -134,11 +133,11 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return $database return $database->getAuthorization()->skip(fn () => $database
->find('keys', [ ->find('keys', [
Query::equal('projectInternalId', [$document->getSequence()]), Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
]); ]));
} }
); );
@@ -148,11 +147,11 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return $database return $database->getAuthorization()->skip(fn () => $database
->find('devKeys', [ ->find('devKeys', [
Query::equal('projectInternalId', [$document->getSequence()]), Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
]); ]));
} }
); );
@@ -162,11 +161,11 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return $database return $database->getAuthorization()->skip(fn () => $database
->find('webhooks', [ ->find('webhooks', [
Query::equal('projectInternalId', [$document->getSequence()]), Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
]); ]));
} }
); );
@@ -176,7 +175,7 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database->find('sessions', [ return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
Query::equal('userInternalId', [$document->getSequence()]), Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
])); ]));
@@ -189,7 +188,7 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database return $database->getAuthorization()->skip(fn () => $database
->find('tokens', [ ->find('tokens', [
Query::equal('userInternalId', [$document->getSequence()]), Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
@@ -203,7 +202,7 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database return $database->getAuthorization()->skip(fn () => $database
->find('challenges', [ ->find('challenges', [
Query::equal('userInternalId', [$document->getSequence()]), Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
@@ -217,7 +216,7 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database return $database->getAuthorization()->skip(fn () => $database
->find('authenticators', [ ->find('authenticators', [
Query::equal('userInternalId', [$document->getSequence()]), Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
@@ -231,7 +230,7 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database return $database->getAuthorization()->skip(fn () => $database
->find('memberships', [ ->find('memberships', [
Query::equal('userInternalId', [$document->getSequence()]), Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
@@ -251,14 +250,14 @@ Database::addFilter(
default => ['function', 'site'] default => ['function', 'site']
}; };
return $database return $database->getAuthorization()->skip(fn () => $database
->find('variables', [ ->find('variables', [
Query::equal('resourceInternalId', [$document->getSequence()]), Query::equal('resourceInternalId', [$document->getSequence()]),
Query::equal('resourceType', $resourceType), Query::equal('resourceType', $resourceType),
Query::orderAsc('resourceType'), Query::orderAsc('resourceType'),
Query::orderAsc(), Query::orderAsc(),
Query::limit(APP_LIMIT_SUBQUERY), Query::limit(APP_LIMIT_SUBQUERY),
]); ]));
} }
); );
@@ -294,11 +293,11 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return $database return $database->getAuthorization()->skip(fn () => $database
->find('variables', [ ->find('variables', [
Query::equal('resourceType', ['project']), Query::equal('resourceType', ['project']),
Query::limit(APP_LIMIT_SUBQUERY) Query::limit(APP_LIMIT_SUBQUERY)
]); ]));
} }
); );
@@ -331,7 +330,7 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database return $database->getAuthorization()->skip(fn () => $database
->find('targets', [ ->find('targets', [
Query::equal('userInternalId', [$document->getSequence()]), Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY) Query::limit(APP_LIMIT_SUBQUERY)
@@ -345,7 +344,7 @@ Database::addFilter(
return; return;
}, },
function (mixed $value, Document $document, Database $database) { function (mixed $value, Document $document, Database $database) {
$targetIds = Authorization::skip(fn () => \array_map( $targetIds = $database->getAuthorization()->skip(fn () => \array_map(
fn ($document) => $document->getAttribute('targetInternalId'), fn ($document) => $document->getAttribute('targetInternalId'),
$database->find('subscribers', [ $database->find('subscribers', [
Query::equal('topicInternalId', [$document->getSequence()]), Query::equal('topicInternalId', [$document->getSequence()]),
+59 -48
View File
@@ -224,7 +224,7 @@ App::setResource('allowedSchemes', function (Document $project) {
/** /**
* Rule associated with a request origin. * Rule associated with a request origin.
*/ */
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project) { App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST); $domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
if (empty($domain)) { if (empty($domain)) {
return new Document(); return new Document();
@@ -232,7 +232,7 @@ App::setResource('rule', function (Request $request, Database $dbForPlatform, Do
// TODO: (@Meldiron) Remove after 1.7.x migration // TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$rule = Authorization::skip(function () use ($dbForPlatform, $domain, $isMd5) { $rule = $authorization->skip(function () use ($dbForPlatform, $domain, $isMd5) {
if ($isMd5) { if ($isMd5) {
return $dbForPlatform->getDocument('rules', md5($domain)); return $dbForPlatform->getDocument('rules', md5($domain));
} }
@@ -247,7 +247,7 @@ App::setResource('rule', function (Request $request, Database $dbForPlatform, Do
} }
return $rule; return $rule;
}, ['request', 'dbForPlatform', 'project']); }, ['request', 'dbForPlatform', 'project', 'authorization']);
/** /**
* CORS service * CORS service
@@ -281,6 +281,7 @@ App::setResource('cors', fn (array $allowedHostnames) => new Cors(
'X-SDK-Language', 'X-SDK-Language',
'X-SDK-Platform', 'X-SDK-Platform',
'X-SDK-GraphQL', 'X-SDK-GraphQL',
'X-SDK-Profile',
// Caching // Caching
'Range', 'Range',
'Cache-Control', 'Cache-Control',
@@ -313,7 +314,7 @@ App::setResource('redirectValidator', function (Document $devKey, array $allowed
return new Redirect($allowedHostnames, $allowedSchemes); return new Redirect($allowedHostnames, $allowedSchemes);
}, ['devKey', '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) { App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
/** /**
* Handles user authentication and session validation. * Handles user authentication and session validation.
* *
@@ -333,7 +334,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
* overwriting the previous value. * overwriting the previous value.
*/ */
Authorization::setDefaultStatus(true); $authorization->setDefaultStatus(true);
$store->setKey('a_session_' . $project->getId()); $store->setKey('a_session_' . $project->getId());
@@ -400,7 +401,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
} }
// if (APP_MODE_ADMIN === $mode) { // if (APP_MODE_ADMIN === $mode) {
// if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) { // if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
// Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. // $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
// } else { // } else {
// $user = new Document([]); // $user = new Document([]);
// } // }
@@ -432,9 +433,9 @@ App::setResource('user', function (string $mode, Document $project, Document $co
$dbForPlatform->setMetadata('user', $user->getId()); $dbForPlatform->setMetadata('user', $user->getId());
return $user; return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']); }, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
App::setResource('project', function ($dbForPlatform, $request, $console) { App::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
/** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Database\Database $dbForPlatform */ /** @var Utopia\Database\Database $dbForPlatform */
/** @var Utopia\Database\Document $console */ /** @var Utopia\Database\Document $console */
@@ -445,10 +446,10 @@ App::setResource('project', function ($dbForPlatform, $request, $console) {
return $console; return $console;
} }
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
return $project; return $project;
}, ['dbForPlatform', 'request', 'console']); }, ['dbForPlatform', 'request', 'console', 'authorization']);
App::setResource('session', function (User $user, Store $store, Token $proofForToken) { App::setResource('session', function (User $user, Store $store, Token $proofForToken) {
if ($user->isEmpty()) { if ($user->isEmpty()) {
@@ -471,10 +472,6 @@ App::setResource('session', function (User $user, Store $store, Token $proofForT
return; return;
}, ['user', 'store', 'proofForToken']); }, ['user', 'store', 'proofForToken']);
App::setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
App::setResource('store', function (): Store { App::setResource('store', function (): Store {
return new Store(); return new Store();
}); });
@@ -505,7 +502,15 @@ App::setResource('proofForCode', function (): Code {
return $code; return $code;
}); });
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) { App::setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
App::setResource('authorization', function () {
return new Authorization();
}, []);
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') { if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform; return $dbForPlatform;
} }
@@ -521,6 +526,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
$database = new Database($adapter, $cache); $database = new Database($adapter, $cache);
$database $database
->setAuthorization($authorization)
->setMetadata('host', \gethostname()) ->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId()) ->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
@@ -542,13 +548,15 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
} }
return $database; return $database;
}, ['pools', 'dbForPlatform', 'cache', 'project']); }, ['pools', 'dbForPlatform', 'cache', 'project', 'authorization']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$adapter = new DatabasePool($pools->get('console')); $adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, $cache); $database = new Database($adapter, $cache);
$database $database
->setAuthorization($authorization)
->setNamespace('_console') ->setNamespace('_console')
->setMetadata('host', \gethostname()) ->setMetadata('host', \gethostname())
->setMetadata('project', 'console') ->setMetadata('project', 'console')
@@ -558,12 +566,12 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$database->setDocumentType('users', User::class); $database->setDocumentType('users', User::class);
return $database; return $database;
}, ['pools', 'cache']); }, ['pools', 'cache', 'authorization']);
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) { App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
$databases = []; $databases = [];
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) { return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') { if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform; return $dbForPlatform;
} }
@@ -575,13 +583,15 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
$dsn = new DSN('mysql://' . $project->getAttribute('database')); $dsn = new DSN('mysql://' . $project->getAttribute('database'));
} }
$configure = (function (Database $database) use ($project, $dsn) { $configure = (function (Database $database) use ($project, $dsn, $authorization) {
$database $database
->setAuthorization($authorization)
->setMetadata('host', \gethostname()) ->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId()) ->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES)
$database->setDocumentType('users', User::class); ->setDocumentType('users', User::class)
;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -611,12 +621,12 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
return $database; return $database;
}; };
}, ['pools', 'dbForPlatform', 'cache']); }, ['pools', 'dbForPlatform', 'cache', 'authorization']);
App::setResource('getLogsDB', function (Group $pools, Cache $cache) { App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null; $database = null;
return function (?Document $project = null) use ($pools, $cache, &$database) { return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') { if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int) $project->getSequence()); $database->setTenant((int) $project->getSequence());
return $database; return $database;
@@ -626,6 +636,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = new Database($adapter, $cache); $database = new Database($adapter, $cache);
$database $database
->setAuthorization($authorization)
->setSharedTables(true) ->setSharedTables(true)
->setNamespace('logsV1') ->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
@@ -638,7 +649,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database; return $database;
}; };
}, ['pools', 'cache']); }, ['pools', 'cache', 'authorization']);
App::setResource('telemetry', fn () => new NoTelemetry()); App::setResource('telemetry', fn () => new NoTelemetry());
@@ -832,7 +843,7 @@ App::setResource('promiseAdapter', function ($register) {
return $register->get('promiseAdapter'); return $register->get('promiseAdapter');
}, ['register']); }, ['register']);
App::setResource('schema', function ($utopia, $dbForProject) { App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
$complexity = function (int $complexity, array $args) { $complexity = function (int $complexity, array $args) {
$queries = Query::parseQueries($args['queries'] ?? []); $queries = Query::parseQueries($args['queries'] ?? []);
@@ -842,8 +853,8 @@ App::setResource('schema', function ($utopia, $dbForProject) {
return $complexity * $limit; return $complexity * $limit;
}; };
$attributes = function (int $limit, int $offset) use ($dbForProject) { $attributes = function (int $limit, int $offset) use ($dbForProject, $authorization) {
$attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [ $attrs = $authorization->skip(fn () => $dbForProject->find('attributes', [
Query::limit($limit), Query::limit($limit),
Query::offset($offset), Query::offset($offset),
])); ]));
@@ -917,7 +928,7 @@ App::setResource('schema', function ($utopia, $dbForProject) {
$urls, $urls,
$params, $params,
); );
}, ['utopia', 'dbForProject']); }, ['utopia', 'dbForProject', 'authorization']);
App::setResource('gitHub', function (Cache $cache) { App::setResource('gitHub', function (Cache $cache) {
return new VcsGitHub($cache); return new VcsGitHub($cache);
@@ -945,7 +956,7 @@ App::setResource('smsRates', function () {
return []; return [];
}); });
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform) { App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', '')); $devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
// Check if given key match project's development keys // Check if given key match project's development keys
@@ -964,7 +975,7 @@ App::setResource('devKey', function (Request $request, Document $project, array
$accessedAt = $key->getAttribute('accessedAt', 0); $accessedAt = $key->getAttribute('accessedAt', 0);
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$key->setAttribute('accessedAt', DatabaseDateTime::now()); $key->setAttribute('accessedAt', DatabaseDateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key)); $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
$dbForPlatform->purgeCachedDocument('projects', $project->getId()); $dbForPlatform->purgeCachedDocument('projects', $project->getId());
} }
@@ -981,15 +992,15 @@ App::setResource('devKey', function (Request $request, Document $project, array
/** Update access time as well */ /** Update access time as well */
$key->setAttribute('accessedAt', DatabaseDateTime::now()); $key->setAttribute('accessedAt', DatabaseDateTime::now());
$key = Authorization::skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key)); $key = $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
$dbForPlatform->purgeCachedDocument('projects', $project->getId()); $dbForPlatform->purgeCachedDocument('projects', $project->getId());
} }
} }
return $key; return $key;
}, ['request', 'project', 'servers', 'dbForPlatform']); }, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']);
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) { App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request, Authorization $authorization) {
$teamInternalId = ''; $teamInternalId = '';
if ($project->getId() !== 'console') { if ($project->getId() !== 'console') {
$teamInternalId = $project->getAttribute('teamInternalId', ''); $teamInternalId = $project->getAttribute('teamInternalId', '');
@@ -999,7 +1010,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
if (str_starts_with($path, '/v1/projects/:projectId')) { if (str_starts_with($path, '/v1/projects/:projectId')) {
$uri = $request->getURI(); $uri = $request->getURI();
$pid = explode('/', $uri)[3]; $pid = explode('/', $uri)[3];
$p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid)); $p = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $pid));
$teamInternalId = $p->getAttribute('teamInternalId', ''); $teamInternalId = $p->getAttribute('teamInternalId', '');
} elseif ($path === '/v1/projects') { } elseif ($path === '/v1/projects') {
$teamId = $request->getParam('teamId', ''); $teamId = $request->getParam('teamId', '');
@@ -1008,7 +1019,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
return new Document([]); return new Document([]);
} }
$team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId)); $team = $authorization->skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
return $team; return $team;
} }
} }
@@ -1017,14 +1028,14 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
return new Document([]); return new Document([]);
} }
$team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) { $team = $authorization->skip(function () use ($dbForPlatform, $teamInternalId) {
return $dbForPlatform->findOne('teams', [ return $dbForPlatform->findOne('teams', [
Query::equal('$sequence', [$teamInternalId]), Query::equal('$sequence', [$teamInternalId]),
]); ]);
}); });
return $team; return $team;
}, ['project', 'dbForPlatform', 'utopia', 'request']); }, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']);
App::setResource( App::setResource(
'isResourceBlocked', 'isResourceBlocked',
@@ -1062,7 +1073,7 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
App::setResource('executor', fn () => new Executor()); App::setResource('executor', fn () => new Executor());
App::setResource('resourceToken', function ($project, $dbForProject, $request) { App::setResource('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
$tokenJWT = $request->getParam('token'); $tokenJWT = $request->getParam('token');
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
@@ -1080,7 +1091,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
return new Document([]); return new Document([]);
} }
$token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId)); $token = $authorization->skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
if ($token->isEmpty()) { if ($token->isEmpty()) {
return new Document([]); return new Document([]);
@@ -1098,7 +1109,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
} }
return match ($token->getAttribute('resourceType')) { return match ($token->getAttribute('resourceType')) {
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject) { TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject, $authorization) {
$sequences = explode(':', $token->getAttribute('resourceInternalId')); $sequences = explode(':', $token->getAttribute('resourceInternalId'));
$ids = explode(':', $token->getAttribute('resourceId')); $ids = explode(':', $token->getAttribute('resourceId'));
@@ -1109,7 +1120,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
$accessedAt = $token->getAttribute('accessedAt', 0); $accessedAt = $token->getAttribute('accessedAt', 0);
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) { if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
$token->setAttribute('accessedAt', DatabaseDateTime::now()); $token->setAttribute('accessedAt', DatabaseDateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token)); $authorization->skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
} }
return new Document([ return new Document([
@@ -1124,8 +1135,8 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
}; };
} }
return new Document([]); return new Document([]);
}, ['project', 'dbForProject', 'request']); }, ['project', 'dbForProject', 'request', 'authorization']);
App::setResource('transactionState', function (Database $dbForProject) { App::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) {
return new TransactionState($dbForProject); return new TransactionState($dbForProject, $authorization);
}, ['dbForProject']); }, ['dbForProject', 'authorization']);
+30 -11
View File
@@ -32,7 +32,6 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role; use Utopia\Database\Helpers\Role;
use Utopia\Database\Query; use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN; use Utopia\DSN\DSN;
use Utopia\Logger\Log; use Utopia\Logger\Log;
use Utopia\Pools\Group; use Utopia\Pools\Group;
@@ -309,7 +308,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
'value' => '{}' 'value' => '{}'
]); ]);
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document)); $statsDocument = $database->getAuthorization()->skip(fn () => $database->createDocument('realtime', $document));
break; break;
} catch (Throwable) { } catch (Throwable) {
Console::warning("Collection not ready. Retrying connection ({$attempts})..."); Console::warning("Collection not ready. Retrying connection ({$attempts})...");
@@ -339,7 +338,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
->setAttribute('timestamp', DateTime::now()) ->setAttribute('timestamp', DateTime::now())
->setAttribute('value', json_encode($payload)); ->setAttribute('value', json_encode($payload));
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument)); $database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (Throwable $th) { } catch (Throwable $th) {
$logError($th, "updateWorkerDocument"); $logError($th, "updateWorkerDocument");
} }
@@ -370,7 +369,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$payload = []; $payload = [];
$list = Authorization::skip(fn () => $database->find('realtime', [ $list = $database->getAuthorization()->skip(fn () => $database->find('realtime', [
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)), Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
])); ]));
@@ -464,13 +463,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) { if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId])); $connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
$consoleDatabase = getConsoleDB(); $consoleDatabase = getConsoleDB();
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId)); $project = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$database = getProjectDB($project); $database = getProjectDB($project);
/** @var Appwrite\Utopia\Database\Documents\User $user */ /** @var Appwrite\Utopia\Database\Documents\User $user */
$user = $database->getDocument('users', $userId); $user = $database->getDocument('users', $userId);
$roles = $user->getRoles(); $roles = $user->getRoles($database->getAuthorization());
$channels = $realtime->connections[$connection]['channels']; $channels = $realtime->connections[$connection]['channels'];
$realtime->unsubscribe($connection); $realtime->unsubscribe($connection);
@@ -526,6 +525,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
try { try {
/** @var Document $project */ /** @var Document $project */
$project = $app->getResource('project'); $project = $app->getResource('project');
$authorization = $app->getResource('authorization');
/* /*
* Project Check * Project Check
@@ -537,7 +537,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
if ( if (
array_key_exists('realtime', $project->getAttribute('apis', [])) array_key_exists('realtime', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['realtime'] && !$project->getAttribute('apis', [])['realtime']
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles())) && !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) { ) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
} }
@@ -573,7 +573,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription()); throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
} }
$roles = $user->getRoles(); $roles = $user->getRoles($authorization);
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId()); $channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
@@ -586,6 +586,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$realtime->subscribe($project->getId(), $connection, $roles, $channels); $realtime->subscribe($project->getId(), $connection, $roles, $channels);
$realtime->connections[$connection]['authorization'] = $authorization;
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT); $user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([ $server->send([$connection], json_encode([
@@ -614,6 +616,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$code = 500; $code = 500;
} }
$message = $th->getMessage(); $message = $th->getMessage();
// sanitize 0 && 5xx errors // sanitize 0 && 5xx errors
@@ -643,12 +646,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) { $server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
try { try {
$response = new Response(new SwooleResponse()); $response = new Response(new SwooleResponse());
$projectId = $realtime->connections[$connection]['projectId']; $projectId = $realtime->connections[$connection]['projectId'] ?? null;
// Get authorization from connection (stored during onOpen)
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$database = getConsoleDB(); $database = getConsoleDB();
$database->setAuthorization($authorization);
if ($projectId !== 'console') { if ($projectId !== 'console') {
$project = Authorization::skip(fn () => $database->getDocument('projects', $projectId)); $project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
$database = getProjectDB($project); $database = getProjectDB($project);
$database->setAuthorization($authorization);
} else { } else {
$project = null; $project = null;
} }
@@ -712,10 +722,19 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.'); throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
} }
$roles = $user->getRoles(); $roles = $user->getRoles($database->getAuthorization());
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId()); $channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
// Preserve authorization before subscribe overwrites the connection array
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels); $realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
// Restore authorization after subscribe
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
$user = $response->output($user, Response::MODEL_ACCOUNT); $user = $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([ $server->send([$connection], json_encode([
'type' => 'response', 'type' => 'response',
+36 -19
View File
@@ -46,19 +46,30 @@ use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry; use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Telemetry\Adapter\None as NoTelemetry; use Utopia\Telemetry\Adapter\None as NoTelemetry;
Authorization::disable();
Runtime::enableCoroutine(); Runtime::enableCoroutine();
Server::setResource('register', fn () => $register); Server::setResource('register', fn () => $register);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) { Server::setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register, Authorization $authorization) {
$pools = $register->get('pools'); $pools = $register->get('pools');
$adapter = new DatabasePool($pools->get('console')); $adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache); $dbForPlatform = new Database($adapter, $cache);
$dbForPlatform->setNamespace('_console');
$dbForPlatform->setDocumentType('users', User::class); $dbForPlatform
->setAuthorization($authorization)
->setNamespace('_console')
->setDocumentType('users', User::class)
;
return $dbForPlatform; return $dbForPlatform;
}, ['cache', 'register']); }, ['cache', 'register', 'authorization']);
Server::setResource('project', function (Message $message, Database $dbForPlatform) { Server::setResource('project', function (Message $message, Database $dbForPlatform) {
$payload = $message->getPayload() ?? []; $payload = $message->getPayload() ?? [];
@@ -71,7 +82,7 @@ Server::setResource('project', function (Message $message, Database $dbForPlatfo
return $dbForPlatform->getDocument('projects', $project->getId()); return $dbForPlatform->getDocument('projects', $project->getId());
}, ['message', 'dbForPlatform']); }, ['message', 'dbForPlatform']);
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) { Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') { if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform; return $dbForPlatform;
} }
@@ -103,15 +114,17 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
->setNamespace('_' . $project->getSequence()); ->setNamespace('_' . $project->getSequence());
} }
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER); $database
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database; return $database;
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']); }, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) { Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database { return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
if ($project->isEmpty() || $project->getId() === 'console') { if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform; return $dbForPlatform;
} }
@@ -125,7 +138,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
if (isset($databases[$dsn->getHost()])) { if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()]; $database = $databases[$dsn->getHost()];
$database->setAuthorization($authorization);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) { if (\in_array($dsn->getHost(), $sharedTables)) {
@@ -162,15 +175,17 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
->setNamespace('_' . $project->getSequence()); ->setNamespace('_' . $project->getSequence());
} }
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER); $database
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database; return $database;
}; };
}, ['pools', 'dbForPlatform', 'cache']); }, ['pools', 'dbForPlatform', 'cache', 'authorization']);
Server::setResource('getLogsDB', function (Group $pools, Cache $cache) { Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null; $database = null;
return function (?Document $project = null) use ($pools, $cache, $database) { return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') { if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence()); $database->setTenant((int)$project->getSequence());
return $database; return $database;
@@ -180,10 +195,11 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = new Database($adapter, $cache); $database = new Database($adapter, $cache);
$database $database
->setAuthorization($authorization)
->setSharedTables(true) ->setSharedTables(true)
->setNamespace('logsV1') ->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
// set tenant // set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
@@ -192,7 +208,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database; return $database;
}; };
}, ['pools', 'cache']); }, ['pools', 'cache', 'authorization']);
Server::setResource('abuseRetention', function () { Server::setResource('abuseRetention', function () {
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
@@ -494,7 +510,8 @@ $worker
->inject('log') ->inject('log')
->inject('pools') ->inject('pools')
->inject('project') ->inject('project')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) { ->inject('authorization')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($worker, $queueName) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN'); $version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) { if ($logger) {
@@ -510,7 +527,7 @@ $worker
$log->addExtra('file', $error->getFile()); $log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine()); $log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString()); $log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles()); $log->addExtra('roles', $authorization->getRoles());
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; $isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
+1 -1
View File
@@ -52,7 +52,7 @@
"utopia-php/cache": "0.13.*", "utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*", "utopia-php/cli": "0.15.*",
"utopia-php/config": "1.*.*", "utopia-php/config": "1.*.*",
"utopia-php/database": "3.*", "utopia-php/database": "4.*",
"utopia-php/detector": "0.2.*", "utopia-php/detector": "0.2.*",
"utopia-php/domains": "0.9.*", "utopia-php/domains": "0.9.*",
"utopia-php/emails": "0.6.*", "utopia-php/emails": "0.6.*",
Generated
+21 -21
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7c9cb03eb5267f1e7a3ffc037ae22b6a", "content-hash": "99b8126a83fa0cb257df7c34ff2fdef5",
"packages": [ "packages": [
{ {
"name": "adhocore/jwt", "name": "adhocore/jwt",
@@ -3552,21 +3552,21 @@
}, },
{ {
"name": "utopia-php/audit", "name": "utopia-php/audit",
"version": "1.0.2", "version": "1.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/utopia-php/audit.git", "url": "https://github.com/utopia-php/audit.git",
"reference": "8c17065c2473d4ca799f65585ca74eb53e1be211" "reference": "15656acfddb9d6f03c395b73673fc66c793c10a5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/8c17065c2473d4ca799f65585ca74eb53e1be211", "url": "https://api.github.com/repos/utopia-php/audit/zipball/15656acfddb9d6f03c395b73673fc66c793c10a5",
"reference": "8c17065c2473d4ca799f65585ca74eb53e1be211", "reference": "15656acfddb9d6f03c395b73673fc66c793c10a5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.0", "php": ">=8.0",
"utopia-php/database": "*" "utopia-php/database": "4.*"
}, },
"require-dev": { "require-dev": {
"laravel/pint": "1.*", "laravel/pint": "1.*",
@@ -3593,9 +3593,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/utopia-php/audit/issues", "issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/1.0.2" "source": "https://github.com/utopia-php/audit/tree/1.0.3"
}, },
"time": "2025-10-20T07:14:26+00:00" "time": "2025-11-04T11:27:42+00:00"
}, },
{ {
"name": "utopia-php/auth", "name": "utopia-php/auth",
@@ -3896,16 +3896,16 @@
}, },
{ {
"name": "utopia-php/database", "name": "utopia-php/database",
"version": "3.6.1", "version": "4.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/utopia-php/database.git", "url": "https://github.com/utopia-php/database.git",
"reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7" "reference": "fe7a1326ad623609e65587fe8c01a630a7075fee"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/c8c1b2f5770245dd4006e2680681e3efbe8b1fa7", "url": "https://api.github.com/repos/utopia-php/database/zipball/fe7a1326ad623609e65587fe8c01a630a7075fee",
"reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7", "reference": "fe7a1326ad623609e65587fe8c01a630a7075fee",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -3948,9 +3948,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/utopia-php/database/issues", "issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/3.6.1" "source": "https://github.com/utopia-php/database/tree/4.3.0"
}, },
"time": "2025-12-16T09:55:41+00:00" "time": "2025-11-14T03:43:10+00:00"
}, },
{ {
"name": "utopia-php/detector", "name": "utopia-php/detector",
@@ -4513,16 +4513,16 @@
}, },
{ {
"name": "utopia-php/migration", "name": "utopia-php/migration",
"version": "1.3.9", "version": "1.3.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/utopia-php/migration.git", "url": "https://github.com/utopia-php/migration.git",
"reference": "c55ec67c74663190cda10fd79297422147be7e85" "reference": "6f366f1d4ac2796e59a97d1ba28cedc355e7122e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/c55ec67c74663190cda10fd79297422147be7e85", "url": "https://api.github.com/repos/utopia-php/migration/zipball/6f366f1d4ac2796e59a97d1ba28cedc355e7122e",
"reference": "c55ec67c74663190cda10fd79297422147be7e85", "reference": "6f366f1d4ac2796e59a97d1ba28cedc355e7122e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -4531,7 +4531,7 @@
"ext-openssl": "*", "ext-openssl": "*",
"php": ">=8.1", "php": ">=8.1",
"utopia-php/console": "0.0.*", "utopia-php/console": "0.0.*",
"utopia-php/database": "3.*", "utopia-php/database": "4.*",
"utopia-php/dsn": "0.2.*", "utopia-php/dsn": "0.2.*",
"utopia-php/storage": "0.18.*" "utopia-php/storage": "0.18.*"
}, },
@@ -4562,9 +4562,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/utopia-php/migration/issues", "issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/1.3.9" "source": "https://github.com/utopia-php/migration/tree/1.3.5"
}, },
"time": "2025-12-08T08:45:09+00:00" "time": "2025-11-25T11:18:29+00:00"
}, },
{ {
"name": "utopia-php/mongo", "name": "utopia-php/mongo",
@@ -14,7 +14,13 @@ databases.updateDocument(
"<DATABASE_ID>", // databaseId "<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId "<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId "<DOCUMENT_ID>", // documentId
Map.of("a", "b"), // data (optional) Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 33,
"isAdmin", false
), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional) List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional) "<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> { new CoroutineCallback<>((result, error) -> {
@@ -14,7 +14,13 @@ databases.upsertDocument(
"<DATABASE_ID>", // databaseId "<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId "<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId "<DOCUMENT_ID>", // documentId
Map.of("a", "b"), // data Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 30,
"isAdmin", false
), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional) List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional) "<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> { new CoroutineCallback<>((result, error) -> {
@@ -14,7 +14,13 @@ tablesDB.updateRow(
"<DATABASE_ID>", // databaseId "<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId "<TABLE_ID>", // tableId
"<ROW_ID>", // rowId "<ROW_ID>", // rowId
Map.of("a", "b"), // data (optional) Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 33,
"isAdmin", false
), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional) List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional) "<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> { new CoroutineCallback<>((result, error) -> {
@@ -14,7 +14,13 @@ tablesDB.upsertRow(
"<DATABASE_ID>", // databaseId "<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId "<TABLE_ID>", // tableId
"<ROW_ID>", // rowId "<ROW_ID>", // rowId
Map.of("a", "b"), // data (optional) Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 33,
"isAdmin", false
), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional) List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional) "<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> { new CoroutineCallback<>((result, error) -> {
@@ -14,7 +14,13 @@ val result = databases.updateDocument(
databaseId = "<DATABASE_ID>", databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>", collectionId = "<COLLECTION_ID>",
documentId = "<DOCUMENT_ID>", documentId = "<DOCUMENT_ID>",
data = mapOf( "a" to "b" ), // (optional) data = mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 33,
"isAdmin" to false
), // (optional)
permissions = listOf(Permission.read(Role.any())), // (optional) permissions = listOf(Permission.read(Role.any())), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional) transactionId = "<TRANSACTION_ID>", // (optional)
) )
@@ -14,7 +14,13 @@ val result = databases.upsertDocument(
databaseId = "<DATABASE_ID>", databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>", collectionId = "<COLLECTION_ID>",
documentId = "<DOCUMENT_ID>", documentId = "<DOCUMENT_ID>",
data = mapOf( "a" to "b" ), data = mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 30,
"isAdmin" to false
), // (optional)
permissions = listOf(Permission.read(Role.any())), // (optional) permissions = listOf(Permission.read(Role.any())), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional) transactionId = "<TRANSACTION_ID>", // (optional)
) )
@@ -14,7 +14,13 @@ val result = tablesDB.updateRow(
databaseId = "<DATABASE_ID>", databaseId = "<DATABASE_ID>",
tableId = "<TABLE_ID>", tableId = "<TABLE_ID>",
rowId = "<ROW_ID>", rowId = "<ROW_ID>",
data = mapOf( "a" to "b" ), // (optional) data = mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 33,
"isAdmin" to false
), // (optional)
permissions = listOf(Permission.read(Role.any())), // (optional) permissions = listOf(Permission.read(Role.any())), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional) transactionId = "<TRANSACTION_ID>", // (optional)
) )
@@ -14,7 +14,13 @@ val result = tablesDB.upsertRow(
databaseId = "<DATABASE_ID>", databaseId = "<DATABASE_ID>",
tableId = "<TABLE_ID>", tableId = "<TABLE_ID>",
rowId = "<ROW_ID>", rowId = "<ROW_ID>",
data = mapOf( "a" to "b" ), // (optional) data = mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 33,
"isAdmin" to false
), // (optional)
permissions = listOf(Permission.read(Role.any())), // (optional) permissions = listOf(Permission.read(Role.any())), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional) transactionId = "<TRANSACTION_ID>", // (optional)
) )
@@ -10,7 +10,13 @@ let document = try await databases.updateDocument(
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>", collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>", documentId: "<DOCUMENT_ID>",
data: [:], // optional data: [
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
], // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: "<TRANSACTION_ID>" // optional transactionId: "<TRANSACTION_ID>" // optional
) )
@@ -10,7 +10,13 @@ let document = try await databases.upsertDocument(
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>", collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>", documentId: "<DOCUMENT_ID>",
data: [:], data: [
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
], // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: "<TRANSACTION_ID>" // optional transactionId: "<TRANSACTION_ID>" // optional
) )
@@ -10,7 +10,13 @@ let row = try await tablesDB.updateRow(
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>", tableId: "<TABLE_ID>",
rowId: "<ROW_ID>", rowId: "<ROW_ID>",
data: [:], // optional data: [
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
], // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: "<TRANSACTION_ID>" // optional transactionId: "<TRANSACTION_ID>" // optional
) )
@@ -10,7 +10,13 @@ let row = try await tablesDB.upsertRow(
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>", tableId: "<TABLE_ID>",
rowId: "<ROW_ID>", rowId: "<ROW_ID>",
data: [:], // optional data: [
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
], // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: "<TRANSACTION_ID>" // optional transactionId: "<TRANSACTION_ID>" // optional
) )
@@ -12,7 +12,13 @@ Document result = await databases.updateDocument(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional transactionId: '<TRANSACTION_ID>', // optional
); );
@@ -12,7 +12,13 @@ Document result = await databases.upsertDocument(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional transactionId: '<TRANSACTION_ID>', // optional
); );
@@ -12,7 +12,13 @@ Row result = await tablesDB.updateRow(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional transactionId: '<TRANSACTION_ID>', // optional
); );
@@ -12,7 +12,13 @@ Row result = await tablesDB.upsertRow(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional transactionId: '<TRANSACTION_ID>', // optional
); );
@@ -3,7 +3,7 @@ mutation {
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>", collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>", documentId: "<DOCUMENT_ID>",
data: "{}", data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}",
permissions: ["read("any")"], permissions: ["read("any")"],
transactionId: "<TRANSACTION_ID>" transactionId: "<TRANSACTION_ID>"
) { ) {
@@ -3,7 +3,7 @@ mutation {
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>", collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>", documentId: "<DOCUMENT_ID>",
data: "{}", data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}",
permissions: ["read("any")"], permissions: ["read("any")"],
transactionId: "<TRANSACTION_ID>" transactionId: "<TRANSACTION_ID>"
) { ) {
@@ -3,7 +3,7 @@ mutation {
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>", tableId: "<TABLE_ID>",
rowId: "<ROW_ID>", rowId: "<ROW_ID>",
data: "{}", data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}",
permissions: ["read("any")"], permissions: ["read("any")"],
transactionId: "<TRANSACTION_ID>" transactionId: "<TRANSACTION_ID>"
) { ) {
@@ -3,7 +3,7 @@ mutation {
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>", tableId: "<TABLE_ID>",
rowId: "<ROW_ID>", rowId: "<ROW_ID>",
data: "{}", data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}",
permissions: ["read("any")"], permissions: ["read("any")"],
transactionId: "<TRANSACTION_ID>" transactionId: "<TRANSACTION_ID>"
) { ) {
@@ -10,7 +10,13 @@ const result = await databases.updateDocument({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: ["read("any")"], // optional permissions: ["read("any")"], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await databases.upsertDocument({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
}, // optional
permissions: ["read("any")"], // optional permissions: ["read("any")"], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await tablesDB.updateRow({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: ["read("any")"], // optional permissions: ["read("any")"], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await tablesDB.upsertRow({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: ["read("any")"], // optional permissions: ["read("any")"], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -3,4 +3,6 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"email": "email@example.com", "email": "email@example.com",
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"userId": "<USER_ID>", "userId": "<USER_ID>",
@@ -3,4 +3,6 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"userId": "<USER_ID>", "userId": "<USER_ID>",
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"factor": "email" "factor": "email"
@@ -2,3 +2,5 @@ GET /v1/account/sessions/oauth2/{provider} HTTP/1.1
Host: cloud.appwrite.io Host: cloud.appwrite.io
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
@@ -2,3 +2,5 @@ GET /v1/account/tokens/oauth2/{provider} HTTP/1.1
Host: cloud.appwrite.io Host: cloud.appwrite.io
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"userId": "<USER_ID>", "userId": "<USER_ID>",
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"userId": "<USER_ID>", "userId": "<USER_ID>",
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"userId": "<USER_ID>", "userId": "<USER_ID>",
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"userId": "<USER_ID>", "userId": "<USER_ID>",
@@ -3,6 +3,8 @@ Host: cloud.appwrite.io
Content-Type: application/json Content-Type: application/json
X-Appwrite-Response-Format: 1.8.0 X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{ {
"userId": "<USER_ID>", "userId": "<USER_ID>",
@@ -7,7 +7,13 @@ X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT> X-Appwrite-JWT: <YOUR_JWT>
{ {
"data": {}, "data": {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
},
"permissions": ["read(\"any\")"], "permissions": ["read(\"any\")"],
"transactionId": "<TRANSACTION_ID>" "transactionId": "<TRANSACTION_ID>"
} }
@@ -7,7 +7,13 @@ X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT> X-Appwrite-JWT: <YOUR_JWT>
{ {
"data": {}, "data": {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
},
"permissions": ["read(\"any\")"], "permissions": ["read(\"any\")"],
"transactionId": "<TRANSACTION_ID>" "transactionId": "<TRANSACTION_ID>"
} }
@@ -7,7 +7,13 @@ X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT> X-Appwrite-JWT: <YOUR_JWT>
{ {
"data": {}, "data": {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
},
"permissions": ["read(\"any\")"], "permissions": ["read(\"any\")"],
"transactionId": "<TRANSACTION_ID>" "transactionId": "<TRANSACTION_ID>"
} }
@@ -7,7 +7,13 @@ X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT> X-Appwrite-JWT: <YOUR_JWT>
{ {
"data": {}, "data": {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
},
"permissions": ["read(\"any\")"], "permissions": ["read(\"any\")"],
"transactionId": "<TRANSACTION_ID>" "transactionId": "<TRANSACTION_ID>"
} }
@@ -10,7 +10,13 @@ const result = await databases.updateDocument({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await databases.upsertDocument({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await tablesDB.updateRow({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await tablesDB.upsertRow({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -1,5 +1,4 @@
appwrite databases upsert-document \ appwrite databases upsert-document \
--database-id <DATABASE_ID> \ --database-id <DATABASE_ID> \
--collection-id <COLLECTION_ID> \ --collection-id <COLLECTION_ID> \
--document-id <DOCUMENT_ID> \ --document-id <DOCUMENT_ID>
--data '{ "key": "value" }'
@@ -10,7 +10,13 @@ const result = await databases.updateDocument({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -9,7 +9,13 @@ const databases = new Databases(client);
const result = await databases.updateDocuments({ const result = await databases.updateDocuments({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
queries: [], // optional queries: [], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await databases.upsertDocument({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await tablesDB.updateRow({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -9,7 +9,13 @@ const tablesDB = new TablesDB(client);
const result = await tablesDB.updateRows({ const result = await tablesDB.updateRows({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
queries: [], // optional queries: [], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -10,7 +10,13 @@ const result = await tablesDB.upsertRow({
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // optional data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>' // optional transactionId: '<TRANSACTION_ID>' // optional
}); });
@@ -13,7 +13,13 @@ Document result = await databases.updateDocument(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, // (optional) data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // (optional)
permissions: [Permission.read(Role.any())], // (optional) permissions: [Permission.read(Role.any())], // (optional)
transactionId: '<TRANSACTION_ID>', // (optional) transactionId: '<TRANSACTION_ID>', // (optional)
); );
@@ -10,7 +10,13 @@ Databases databases = Databases(client);
DocumentList result = await databases.updateDocuments( DocumentList result = await databases.updateDocuments(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
data: {}, // (optional) data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // (optional)
queries: [], // (optional) queries: [], // (optional)
transactionId: '<TRANSACTION_ID>', // (optional) transactionId: '<TRANSACTION_ID>', // (optional)
); );
@@ -13,7 +13,13 @@ Document result = await databases.upsertDocument(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>', collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>', documentId: '<DOCUMENT_ID>',
data: {}, data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
}, // (optional)
permissions: [Permission.read(Role.any())], // (optional) permissions: [Permission.read(Role.any())], // (optional)
transactionId: '<TRANSACTION_ID>', // (optional) transactionId: '<TRANSACTION_ID>', // (optional)
); );
@@ -13,7 +13,13 @@ Row result = await tablesDB.updateRow(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // (optional) data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // (optional)
permissions: [Permission.read(Role.any())], // (optional) permissions: [Permission.read(Role.any())], // (optional)
transactionId: '<TRANSACTION_ID>', // (optional) transactionId: '<TRANSACTION_ID>', // (optional)
); );
@@ -10,7 +10,13 @@ TablesDB tablesDB = TablesDB(client);
RowList result = await tablesDB.updateRows( RowList result = await tablesDB.updateRows(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
data: {}, // (optional) data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // (optional)
queries: [], // (optional) queries: [], // (optional)
transactionId: '<TRANSACTION_ID>', // (optional) transactionId: '<TRANSACTION_ID>', // (optional)
); );
@@ -13,7 +13,13 @@ Row result = await tablesDB.upsertRow(
databaseId: '<DATABASE_ID>', databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>', tableId: '<TABLE_ID>',
rowId: '<ROW_ID>', rowId: '<ROW_ID>',
data: {}, // (optional) data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // (optional)
permissions: [Permission.read(Role.any())], // (optional) permissions: [Permission.read(Role.any())], // (optional)
transactionId: '<TRANSACTION_ID>', // (optional) transactionId: '<TRANSACTION_ID>', // (optional)
); );
@@ -13,7 +13,13 @@ Document result = await databases.UpdateDocument(
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>", collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>", documentId: "<DOCUMENT_ID>",
data: [object], // optional data: new {
username = "walter.obrien",
email = "walter.obrien@example.com",
fullName = "Walter O'Brien",
age = 33,
isAdmin = false
}, // optional
permissions: new List<string> { Permission.Read(Role.Any()) }, // optional permissions: new List<string> { Permission.Read(Role.Any()) }, // optional
transactionId: "<TRANSACTION_ID>" // optional transactionId: "<TRANSACTION_ID>" // optional
); );
@@ -12,7 +12,13 @@ Databases databases = new Databases(client);
DocumentList result = await databases.UpdateDocuments( DocumentList result = await databases.UpdateDocuments(
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>", collectionId: "<COLLECTION_ID>",
data: [object], // optional data: new {
username = "walter.obrien",
email = "walter.obrien@example.com",
fullName = "Walter O'Brien",
age = 33,
isAdmin = false
}, // optional
queries: new List<string>(), // optional queries: new List<string>(), // optional
transactionId: "<TRANSACTION_ID>" // optional transactionId: "<TRANSACTION_ID>" // optional
); );
@@ -13,7 +13,13 @@ Document result = await databases.UpsertDocument(
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>", collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>", documentId: "<DOCUMENT_ID>",
data: [object], data: new {
username = "walter.obrien",
email = "walter.obrien@example.com",
fullName = "Walter O'Brien",
age = 30,
isAdmin = false
}, // optional
permissions: new List<string> { Permission.Read(Role.Any()) }, // optional permissions: new List<string> { Permission.Read(Role.Any()) }, // optional
transactionId: "<TRANSACTION_ID>" // optional transactionId: "<TRANSACTION_ID>" // optional
); );
@@ -13,7 +13,13 @@ Row result = await tablesDB.UpdateRow(
databaseId: "<DATABASE_ID>", databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>", tableId: "<TABLE_ID>",
rowId: "<ROW_ID>", rowId: "<ROW_ID>",
data: [object], // optional data: new {
username = "walter.obrien",
email = "walter.obrien@example.com",
fullName = "Walter O'Brien",
age = 33,
isAdmin = false
}, // optional
permissions: new List<string> { Permission.Read(Role.Any()) }, // optional permissions: new List<string> { Permission.Read(Role.Any()) }, // optional
transactionId: "<TRANSACTION_ID>" // optional transactionId: "<TRANSACTION_ID>" // optional
); );

Some files were not shown because too many files have changed in this diff Show More