Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-schedule-execution-improvements

This commit is contained in:
loks0n 2024-08-07 17:43:07 +01:00
commit e2ed0b2693
28 changed files with 157 additions and 74 deletions

Binary file not shown.

View file

@ -6,7 +6,8 @@ return [
'magicSession',
'recovery',
'invitation',
'mfaChallenge'
'mfaChallenge',
'sessionAlert'
],
'sms' => [
'verification',

View file

@ -11,4 +11,4 @@
<p>{{footer}}</p>
<p style="margin-bottom: 0px;">{{thanks}}</p>
<p style="margin-top: 0px;">{{signature}}</p>
<p style="margin-top: 0px;">{{signature}}</p>

View file

@ -18,13 +18,13 @@
"emails.magicSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.",
"emails.magicSession.thanks": "Thanks,",
"emails.magicSession.signature": "{{project}} team",
"emails.sessionAlert.subject": "New session alert for {{project}}",
"emails.sessionAlert.subject": "Security alert: new session on your {{project}} account",
"emails.sessionAlert.hello":"Hello {{user}}",
"emails.sessionAlert.body": "We're writing to inform you that a new session has been initiated on your {{b}}{{project}}{{/b}} account, on {{b}}{{dateTime}}{{/b}}. \nHere are the details of the new session: ",
"emails.sessionAlert.body": "A new session has been created on your {{b}}{{project}}{{/b}} account, on {{b}}{{dateTime}}{{/b}}.\nHere are the details of the new session: ",
"emails.sessionAlert.listDevice": "Device: {{b}}{{device}}{{/b}}",
"emails.sessionAlert.listIpAddress": "IP Address: {{b}}{{ipAddress}}{{/b}}",
"emails.sessionAlert.listCountry": "Country: {{b}}{{country}}{{/b}}",
"emails.sessionAlert.footer": "If you didn't request the sign in, you can safely ignore this email. If you suspect unauthorized activity, please secure your account immediately.",
"emails.sessionAlert.footer": "If this was you, there's nothing more you need to do.\nIf you didn't initiate this session or suspect any unauthorized activity, please secure your account.",
"emails.sessionAlert.thanks": "Thanks,",
"emails.sessionAlert.signature": "{{project}} team",
"emails.otpSession.subject": "OTP for {{project}} Login",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -124,7 +124,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
'dateTime' => DateTime::format(new \DateTime(), 'Y-m-d H:i:s'),
'dateTime' => DateTime::format(new \DateTime(), 'h:ia MMMM dS'),
'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'),
'device' => $session->getAttribute('clientName'),
@ -224,7 +224,11 @@ $createSession = function (string $userId, string $secret, Request $request, Res
}
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
if ($dbForProject->count('sessions', [
Query::equal('userId', [$user->getId()]),
]) !== 1) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
}
}
$queueForEvents
@ -904,7 +908,11 @@ App::post('/v1/account/sessions/email')
;
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
if ($dbForProject->count('sessions', [
Query::equal('userId', [$user->getId()]),
]) !== 1) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
}
}
$response->dynamic($session, Response::MODEL_SESSION);

View file

@ -1459,7 +1459,8 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds) {
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@ -1471,13 +1472,23 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$path = $deployment->getAttribute('path');
if(empty($path) || !$deviceForFunctions->exists($path)) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$deploymentId = ID::unique();
$destination = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$deviceForFunctions->transfer($path, $destination, $deviceForFunctions);
$deployment->removeAttribute('$internalId');
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
'$internalId' => '',
'$id' => $deploymentId,
'buildId' => '',
'buildInternalId' => '',
'path' => $destination,
'entrypoint' => $function->getAttribute('entrypoint'),
'commands' => $function->getAttribute('commands', ''),
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),

View file

@ -876,6 +876,14 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers')
->inject('dbForConsole')
->action(function (string $projectId, array $numbers, Response $response, Database $dbForConsole) {
$uniqueNumbers = [];
foreach ($numbers as $number) {
if (isset($uniqueNumbers[$number['phone']])) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Duplicate phone numbers are not allowed.');
}
$uniqueNumbers[$number['phone']] = $number['otp'];
}
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {

View file

@ -999,7 +999,7 @@ $register->set('smtp', function () {
return $mail;
});
$register->set('geodb', function () {
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-02.mmdb');
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-08.mmdb');
});
$register->set('passwordsDictionary', function () {
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');

View file

@ -787,7 +787,7 @@ $image = $this->getParam('image', '');
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.5.5
image: openruntimes/executor:0.6.5
networks:
- appwrite
- runtimes

22
composer.lock generated
View file

@ -1923,16 +1923,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.33.6",
"version": "0.33.7",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6"
"reference": "78d293d99a262bd63ece750bbf989c7e0643b825"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6",
"reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6",
"url": "https://api.github.com/repos/utopia-php/http/zipball/78d293d99a262bd63ece750bbf989c7e0643b825",
"reference": "78d293d99a262bd63ece750bbf989c7e0643b825",
"shasum": ""
},
"require": {
@ -1962,9 +1962,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.33.6"
"source": "https://github.com/utopia-php/http/tree/0.33.7"
},
"time": "2024-03-21T18:10:57+00:00"
"time": "2024-08-01T14:01:04+00:00"
},
{
"name": "utopia-php/image",
@ -3158,16 +3158,16 @@
},
{
"name": "laravel/pint",
"version": "v1.17.0",
"version": "v1.17.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5"
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
"reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
"url": "https://api.github.com/repos/laravel/pint/zipball/b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
"shasum": ""
},
"require": {
@ -3220,7 +3220,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2024-07-23T16:40:20+00:00"
"time": "2024-08-01T09:06:33+00:00"
},
{
"name": "matthiasmullie/minify",

View file

@ -873,7 +873,7 @@ services:
hostname: exc1
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.6.1
image: openruntimes/executor:0.6.5
restart: unless-stopped
networks:
- appwrite

View file

@ -46,9 +46,9 @@ class MockNumber extends Validator
return false;
}
$otp = new Text(6, 6);
$otp = new Text(6, 6, Text::NUMBERS);
if (!$otp->isValid($value['otp'])) {
$this->message = 'OTP must be a valid string and exactly 6 characters.';
$this->message = 'Invalid OTP. Please make sure the OTP is a 6 digit number';
return false;
}

View file

@ -235,7 +235,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
->setTwitter(APP_SOCIAL_TWITTER_HANDLE)
->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD)
->setDefaultHeaders([
'X-Appwrite-Response-Format' => '1.5.0',
'X-Appwrite-Response-Format' => '1.6.0',
]);
// Make sure we have a clean slate.

View file

@ -333,6 +333,7 @@ class Builds extends Action
$source = $path;
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source));
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole);
}

View file

@ -136,7 +136,11 @@ class BodyMultipart
}
$query .= $eol . $eol;
$query .= $value . $eol;
if ($value === false) {
$query .= 0 . $eol;
} else {
$query .= $value . $eol;
}
$query .= '--' . $this->boundary;
}

View file

@ -110,8 +110,7 @@ class TemplateFunction extends Model
'default' => [],
'example' => [],
'array' => true
])
;
]);
}
/**

View file

@ -33,8 +33,7 @@ class TemplateRuntime extends Model
'description' => 'Path to function in VCS (Version Control System) repository',
'default' => '',
'example' => 'node/starter',
])
;
]);
}
/**

View file

@ -10,37 +10,42 @@ class TemplateVariable extends Model
public function __construct()
{
$this
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Variable Name.',
'default' => '',
'example' => 'APPWRITE_DATABASE_ID',
])
->addRule('description', [
'type' => self::TYPE_STRING,
'description' => 'Variable Description.',
'default' => '',
'example' => 'The ID of the Appwrite database that contains the collection to sync.',
])
->addRule('placeholder', [
'type' => self::TYPE_STRING,
'description' => 'Variable Placeholder.',
'default' => '',
'example' => '64a55...7b912',
])
->addRule('required', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is the variable required?',
'default' => false,
'example' => false,
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Variable Type.',
'default' => '',
'example' => 'password',
])
;
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Variable Name.',
'default' => '',
'example' => 'APPWRITE_DATABASE_ID',
])
->addRule('description', [
'type' => self::TYPE_STRING,
'description' => 'Variable Description.',
'default' => '',
'example' => 'The ID of the Appwrite database that contains the collection to sync.',
])
->addRule('value', [
'type' => self::TYPE_STRING,
'description' => 'Variable Value.',
'default' => '',
'example' => '512',
])
->addRule('placeholder', [
'type' => self::TYPE_STRING,
'description' => 'Variable Placeholder.',
'default' => '',
'example' => '64a55...7b912',
])
->addRule('required', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is the variable required?',
'default' => false,
'example' => false,
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Variable Type.',
'default' => '',
'example' => 'password',
]);
}
/**

View file

@ -168,6 +168,7 @@ class Executor
* @param string $source
* @param string $entrypoint
* @param string $runtimeEntrypoint
* @param bool $logging
*
* @return array
*/
@ -193,8 +194,7 @@ class Executor
}
$runtimeId = "$projectId-$deploymentId";
$route = '/runtimes/' . $runtimeId . '/execution';
$route = '/runtimes/' . $runtimeId . '/executions';
// Remove after migration
if ($version == 'v3') {
@ -216,6 +216,7 @@ class Executor
'version' => $version,
'runtimeEntrypoint' => $runtimeEntrypoint,
'logging' => $logging,
'restartPolicy' => 'always' // Once utopia/orchestration has it, use DockerAPI::ALWAYS (0.13+)
];
if(!empty($body)) {

View file

@ -1225,7 +1225,7 @@ class AccountCustomClientTest extends Scope
$this->assertEquals(201, $response['headers']['status-code']);
// Create a session for the new account
// Create first session for the new account
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
@ -1238,11 +1238,23 @@ class AccountCustomClientTest extends Scope
$this->assertEquals(201, $response['headers']['status-code']);
// Create second session for the new account
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
]), [
'email' => $email,
'password' => $password,
]);
// Check the alert email
$lastEmail = $this->getLastEmail();
$this->assertEquals($email, $lastEmail['to'][0]['address']);
$this->assertStringContainsString('New session alert', $lastEmail['subject']);
$this->assertStringContainsString('Security alert: new session', $lastEmail['subject']);
$this->assertStringContainsString($response['body']['ip'], $lastEmail['text']); // IP Address
$this->assertStringContainsString('Unknown', $lastEmail['text']); // Country
$this->assertStringContainsString($response['body']['clientName'], $lastEmail['text']); // Client name

View file

@ -1592,7 +1592,7 @@ class ProjectsConsoleClientTest extends Scope
]
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and OTP must be a valid string and exactly 6 characters.', $response['body']['message']);
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Invalid OTP. Please make sure the OTP is a 6 digit number', $response['body']['message']);
/** Trying to pass an OTP shorter than 6 characters*/
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/mock-numbers', array_merge([
@ -1607,7 +1607,22 @@ class ProjectsConsoleClientTest extends Scope
]
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and OTP must be a valid string and exactly 6 characters.', $response['body']['message']);
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Invalid OTP. Please make sure the OTP is a 6 digit number', $response['body']['message']);
/** Trying to pass an OTP with non numeric characters */
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/mock-numbers', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'numbers' => [
[
'phone' => '+1655513432',
'otp' => '123re2'
]
]
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Invalid OTP. Please make sure the OTP is a 6 digit number', $response['body']['message']);
/** Trying to pass an invalid phone number */
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/mock-numbers', array_merge([
@ -1639,6 +1654,25 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Phone number must start with a \'+\' can have a maximum of fifteen digits.', $response['body']['message']);
/** Trying to pass duplicate numbers */
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/mock-numbers', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'numbers' => [
[
'phone' => '+1655513432',
'otp' => '123456'
],
[
'phone' => '+1655513432',
'otp' => '123456'
]
]
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals('Duplicate phone numbers are not allowed.', $response['body']['message']);
$numbers = [];
for ($i = 0; $i < 11; $i++) {
$numbers[] = [