Compare commits

..
Author SHA1 Message Date
Jake Barnby d029ed3c83 Update DB connection init 2025-09-17 17:40:53 +12:00
1075 changed files with 5202 additions and 44884 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
> We just announced Transactions API for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-transactions-api)
> We just announced Timestamp Overrides for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-timestamp-overrides)
> Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga)
-5
View File
@@ -103,11 +103,6 @@ CLI::setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
CLI::setResource(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
+4 -3
View File
@@ -1,5 +1,6 @@
<?php
use Appwrite\Auth\Auth;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
@@ -172,7 +173,7 @@ return [
'size' => 256,
'signed' => true,
'required' => false,
'default' => 'argon2',
'default' => Auth::DEFAULT_ALGO,
'array' => false,
'filters' => [],
],
@@ -183,7 +184,7 @@ return [
'size' => 65535,
'signed' => true,
'required' => false,
'default' => ['type' => 'argon2', 'memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3],
'default' => Auth::DEFAULT_ALGO_OPTIONS,
'array' => false,
'filters' => ['json'],
],
@@ -1114,9 +1115,9 @@ return [
[
'$id' => ID::custom('expire'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'required' => false,
'format' => '',
'signed' => false,
'default' => null,
'array' => false,
-124
View File
@@ -2521,128 +2521,4 @@ return [
],
],
],
'transactions' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('transactions'),
'name' => 'Transactions',
'attributes' => [
[
'$id' => ID::custom('status'),
'type' => Database::VAR_STRING,
'size' => 16, // pending | committing | committed | failed
'signed' => true,
'required' => false,
'default' => 'pending',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('operations'),
'type' => Database::VAR_INTEGER,
'size' => 0,
'signed' => false,
'required' => true,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('expiresAt'),
'type' => Database::VAR_DATETIME,
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_expiresAt'),
'type' => Database::INDEX_KEY,
'attributes' => ['expiresAt'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
],
],
],
'transactionLogs' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('transactionLogs'),
'name' => 'Transaction Logs',
'attributes' => [
[
'$id' => ID::custom('transactionInternalId'),
'type' => Database::VAR_STRING,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('databaseInternalId'),
'type' => Database::VAR_STRING,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('collectionInternalId'),
'type' => Database::VAR_STRING,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('documentId'),
'type' => Database::VAR_STRING,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('action'),
'type' => Database::VAR_STRING,
'size' => 32, // create | update | upsert | increment | decrement | delete | bulkCreate | bulkUpdate | bulkUpsert | bulkDelete
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('data'),
'type' => Database::VAR_STRING,
'size' => 5_000_000, // Allow large payloads for bulk operations
'signed' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => ['json'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_transaction'),
'type' => Database::INDEX_KEY,
'attributes' => ['transactionInternalId'],
'lengths' => [],
'orders' => [],
],
],
],
];
+2 -2
View File
@@ -4,6 +4,7 @@
* Initializes console project document.
*/
use Appwrite\Auth\Auth;
use Appwrite\Network\Platform;
use Utopia\Database\Helpers\ID;
use Utopia\System\System;
@@ -37,7 +38,7 @@ $console = [
'mockNumbers' => [],
'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
'duration' => TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled',
'invalidateSessions' => true
],
@@ -48,7 +49,6 @@ $console = [
'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
],
'smtpBaseTemplate' => APP_BRANDED_EMAIL_BASE_TEMPLATE,
];
return $console;
-62
View File
@@ -211,11 +211,6 @@ return [
'description' => 'User with the requested ID could not be found.',
'code' => 404,
],
Exception::USER_EMAIL_NOT_FOUND => [
'name' => Exception::USER_EMAIL_NOT_FOUND,
'description' => 'User email could not be found.',
'code' => 400,
],
Exception::USER_EMAIL_ALREADY_EXISTS => [
'name' => Exception::USER_EMAIL_ALREADY_EXISTS,
'description' => 'A user with the same email already exists in the current project.',
@@ -317,21 +312,11 @@ return [
'description' => 'OAuth2 provider returned some error.',
'code' => 424,
],
Exception::USER_EMAIL_NOT_VERIFIED => [
'name' => Exception::USER_EMAIL_NOT_VERIFIED,
'description' => 'User email is not verified',
'code' => 400,
],
Exception::USER_EMAIL_ALREADY_VERIFIED => [
'name' => Exception::USER_EMAIL_ALREADY_VERIFIED,
'description' => 'User email is already verified',
'code' => 409,
],
Exception::USER_PHONE_NOT_VERIFIED => [
'name' => Exception::USER_PHONE_NOT_VERIFIED,
'description' => 'User phone is not verified',
'code' => 400,
],
Exception::USER_PHONE_ALREADY_VERIFIED => [
'name' => Exception::USER_PHONE_ALREADY_VERIFIED,
'description' => 'User phone is already verified',
@@ -578,11 +563,6 @@ return [
'description' => 'The requested runtime is either inactive or unsupported. Please check the value of the _APP_FUNCTIONS_RUNTIMES environment variable.',
'code' => 404,
],
Exception::FUNCTION_ALREADY_EXISTS => [
'name' => Exception::FUNCTION_ALREADY_EXISTS,
'description' => 'Function with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::FUNCTION_ENTRYPOINT_MISSING => [
'name' => Exception::FUNCTION_ENTRYPOINT_MISSING,
'description' => 'Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function\'s "Settings" > "Configuration" > "Entrypoint".',
@@ -986,48 +966,6 @@ return [
'code' => 409,
],
/** Transactions */
Exception::TRANSACTION_NOT_FOUND => [
'name' => Exception::TRANSACTION_NOT_FOUND,
'description' => 'Transaction with the requested ID could not be found.',
'code' => 404,
],
Exception::TRANSACTION_ALREADY_EXISTS => [
'name' => Exception::TRANSACTION_ALREADY_EXISTS,
'description' => 'Transaction with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::TRANSACTION_INVALID => [
'name' => Exception::TRANSACTION_INVALID,
'description' => 'The transaction is invalid. Please check the transaction state and try again.',
'code' => 400,
],
Exception::TRANSACTION_FAILED => [
'name' => Exception::TRANSACTION_FAILED,
'description' => 'The transaction has errored. Please check the transaction data and try again.',
'code' => 400,
],
Exception::TRANSACTION_EXPIRED => [
'name' => Exception::TRANSACTION_EXPIRED,
'description' => 'The transaction has expired. Please create a new transaction and try again.',
'code' => 410,
],
Exception::TRANSACTION_CONFLICT => [
'name' => Exception::TRANSACTION_CONFLICT,
'description' => 'The transaction has a conflict. Please resolve the conflict and try again.',
'code' => 409,
],
Exception::TRANSACTION_LIMIT_EXCEEDED => [
'name' => Exception::TRANSACTION_LIMIT_EXCEEDED,
'description' => 'The maximum number of operations per transaction has been exceeded.',
'code' => 400,
],
Exception::TRANSACTION_NOT_READY => [
'name' => Exception::TRANSACTION_NOT_READY,
'description' => 'The transaction is not ready yet. Please try again later.',
'code' => 400,
],
/** Project Errors */
Exception::PROJECT_NOT_FOUND => [
'name' => Exception::PROJECT_NOT_FOUND,
@@ -131,14 +131,6 @@
.social-icon > img {
margin: auto;
}
p.security-phrase:not(:empty) {
opacity: 0.7;
margin: 0;
padding: 0;
margin-top: 32px;
padding-top: 32px;
border-top: 1px solid #e8e9f0;
}
</style>
</head>
@@ -155,7 +147,6 @@
<img
height="32px"
src="{{logoUrl}}"
alt="Appwrite logo"
/>
</td>
</tr>
@@ -164,12 +155,12 @@
<table style="margin-top: 32px">
<tr>
<td>
<h1>{{heading}}</h1>
<h1>{{subject}}</h1>
</td>
</tr>
</table>
<table style="margin-top: 16px">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
+1 -21
View File
@@ -44,21 +44,6 @@
color: currentColor;
word-break: break-all;
}
a.button {
box-sizing: border-box;
display: inline-block;
text-align: center;
text-decoration: none;
padding: 9px 14px;
color: #ffffff;
background-color: #2D2D31;
border: 1px solid #414146;
border-radius: 8px;
}
a.button:hover,
a.button:focus {
opacity: 0.8;
}
table {
width: 100%;
border-spacing: 0 !important;
@@ -109,15 +94,10 @@
h* {
font-family: 'Poppins', sans-serif;
}
p {
margin-bottom: 10px;
}
p.security-phrase:not(:empty) {
opacity: 0.7;
margin-top: 32px;
padding-top: 32px;
border-top: 1px solid #e8e9f0;
}
</style>
</head>
@@ -1,6 +1,6 @@
<p>{{hello}}</p>
<p>{{body}}</p>
<p><a href="{{redirect}}" target="_blank" class="button">{{buttonText}}</a></p>
<p><a href="{{redirect}}" target="_blank" style="font-size: 14px; font-family: Inter, sans-serif; color: #ffffff; text-decoration: none; background-color: #2D2D31; border-radius: 8px; padding: 9px 14px; border: 1px solid #414146; display: inline-block; text-align:center; box-sizing: border-box;">{{buttonText}}</a></p>
<p>{{footer}}</p>
<p style="margin-bottom: 32px">
{{thanks}}
@@ -5,7 +5,7 @@
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
<tr>
<td align="center" style="border-radius: 8px; background-color: #19191D;">
<a rel="noopener" target="_blank" href="{{redirect}}" class="button">{{buttonText}}</a>
<a rel="noopener" target="_blank" href="{{redirect}}" style="font-size: 14px; font-family: Inter; color: #ffffff; text-decoration: none; border-radius: 8px; padding: 9px 14px; border: 1px solid #19191D; display: inline-block;">{{buttonText}}</a>
</td>
</tr>
</table>
@@ -5,7 +5,7 @@
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
<tr>
<td align="center" style="border-radius: 8px; background-color: #ffffff;">
<p style="font-size: 24px; text-indent: 18px; letter-spacing: 18px; font-family: 'Inter', sans-serif; color: #414146; text-decoration: none; border-radius: 8px; margin-top: 0px; margin-bottom: 0px; padding: 24px 12px; border: 1px solid #EDEDF0; display: inline-block; font-weight: bold;">{{otp}}</p>
<p style="font-size: 24px; text-indent: 18px; letter-spacing: 18px; font-family: 'Inter', sans-serif; color: #414146; text-decoration: none; border-radius: 8px; padding: 24px 12px; border: 1px solid #EDEDF0; display: inline-block; font-weight: bold; ">{{otp}}</p>
</td>
</tr>
</table>
+4 -2
View File
@@ -5,7 +5,7 @@
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
<tr>
<td align="center" style="border-radius: 8px; background-color: #ffffff;">
<p style="font-size: 24px; text-indent: 18px; letter-spacing: 18px; font-family: 'Inter', sans-serif; color: #414146; text-decoration: none; border-radius: 8px; margin-top: 0px; margin-bottom: 0px; padding: 24px 12px; border: 1px solid #EDEDF0; display: inline-block; font-weight: bold; ">{{otp}}</p>
<p style="font-size: 24px; text-indent: 18px; letter-spacing: 18px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 24px 12px; border: 1px solid #EDEDF0; display: inline-block; font-weight: bold; ">{{otp}}</p>
</td>
</tr>
</table>
@@ -15,4 +15,6 @@
<p style="margin-bottom: 0px;">{{thanks}}</p>
<p style="margin-top: 0px;">{{signature}}</p>
<p class="security-phrase">{{securityPhrase}}</p>
<hr style="margin-block-start: 1rem; margin-block-end: 1rem;">
<p style="opacity: 0.7;">{{securityPhrase}}</p>
+2 -5
View File
@@ -3,9 +3,8 @@
"settings.locale": "en",
"settings.direction": "ltr",
"emails.sender": "{{project}} Team",
"emails.verification.subject": "Account Verification for {{project}}",
"emails.verification.subject": "Account Verification",
"emails.verification.preview": "Verify your email to activate your {{project}} account.",
"emails.verification.heading": "Verify your email to start using {{project}}",
"emails.verification.hello": "Hello {{user}},",
"emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.",
"emails.verification.footer": "If you didnt ask to verify this address, you can ignore this message.",
@@ -34,7 +33,6 @@
"emails.sessionAlert.signature": "{{project}} team",
"emails.otpSession.subject": "OTP for {{project}} Login",
"emails.otpSession.preview": "Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.",
"emails.otpSession.heading": "Login with OTP to use {{project}}",
"emails.otpSession.hello": "Hello {{user}},",
"emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.",
"emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.",
@@ -43,13 +41,12 @@
"emails.otpSession.signature": "{{project}} team",
"emails.mfaChallenge.subject": "Verification Code for {{project}}",
"emails.mfaChallenge.preview": "Use code {{otp}} for two-step verification in {{project}}. Expires in 15 minutes.",
"emails.mfaChallenge.heading": "Complete two-step verification to use {{project}}",
"emails.mfaChallenge.hello": "Hello {{user}},",
"emails.mfaChallenge.description": "Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.",
"emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.",
"emails.mfaChallenge.thanks": "Thanks,",
"emails.mfaChallenge.signature": "{{project}} team",
"emails.recovery.subject": "Password Reset for {{project}}",
"emails.recovery.subject": "Password Reset",
"emails.recovery.preview": "Reset your {{project}} password using the link.",
"emails.recovery.hello": "Hello {{user}},",
"emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.",
+16 -16
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '21.2.1',
'version' => '20.0.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '20.2.1',
'version' => '19.0.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -79,7 +79,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '13.2.2',
'version' => '12.0.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -116,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '11.2.1',
'version' => '10.0.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -139,7 +139,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.17.1',
'version' => '0.13.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -207,7 +207,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '0.1.1',
'version' => '0.1.0',
'url' => '',
'package' => '',
'enabled' => true,
@@ -226,7 +226,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '10.2.1',
'version' => '9.1.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -262,7 +262,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '20.2.1',
'version' => '19.0.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -281,7 +281,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '17.4.1',
'version' => '17.0.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -300,7 +300,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '13.4.1',
'version' => '13.0.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -319,7 +319,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '19.2.1',
'version' => '18.0.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -338,7 +338,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => 'v0.13.1',
'version' => '0.11.0',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => 'https://github.com/appwrite/sdk-for-go',
'enabled' => true,
@@ -357,7 +357,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.21.2',
'version' => '0.17.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -376,7 +376,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '19.2.1',
'version' => '18.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -395,7 +395,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '12.2.1',
'version' => '11.0.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -418,7 +418,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '13.2.2',
'version' => '12.0.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+8 -7
View File
@@ -1,5 +1,6 @@
<?php
use Appwrite\Auth\Auth;
$member = [
'global',
@@ -27,7 +28,7 @@ $member = [
'subscribers.write',
'subscribers.read',
'assistant.read',
'rules.read',
'rules.read'
];
$admins = [
@@ -91,7 +92,7 @@ $admins = [
];
return [
USER_ROLE_GUESTS => [
Auth::USER_ROLE_GUESTS => [
'label' => 'Guests',
'scopes' => [
'global',
@@ -111,23 +112,23 @@ return [
'execution.write',
],
],
USER_ROLE_USERS => [
Auth::USER_ROLE_USERS => [
'label' => 'Users',
'scopes' => \array_merge($member),
],
USER_ROLE_ADMIN => [
Auth::USER_ROLE_ADMIN => [
'label' => 'Admin',
'scopes' => \array_merge($admins),
],
USER_ROLE_DEVELOPER => [
Auth::USER_ROLE_DEVELOPER => [
'label' => 'Developer',
'scopes' => \array_merge($admins),
],
USER_ROLE_OWNER => [
Auth::USER_ROLE_OWNER => [
'label' => 'Owner',
'scopes' => \array_merge($member, $admins),
],
USER_ROLE_APPS => [
Auth::USER_ROLE_APPS => [
'label' => 'Applications',
'scopes' => ['global', 'health.read', 'graphql'],
],
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -3102,7 +3102,7 @@ App::post('/v1/messaging/messages/email')
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE,
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
@@ -3244,7 +3244,7 @@ App::post('/v1/messaging/messages/sms')
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE,
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
@@ -3462,7 +3462,7 @@ App::post('/v1/messaging/messages/push')
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE,
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
@@ -3863,7 +3863,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
$schedule = $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE,
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
@@ -4084,7 +4084,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
$schedule = $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE,
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
@@ -4258,7 +4258,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
$schedule = $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => SCHEDULE_RESOURCE_TYPE_MESSAGE,
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
+3 -23
View File
@@ -1,6 +1,7 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Validator\MockNumber;
use Appwrite\Event\Delete;
use Appwrite\Event\Mail;
@@ -117,7 +118,7 @@ App::post('/v1/projects')
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
'passwordHistory' => 0,
'passwordDictionary' => false,
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
'personalDataCheck' => false,
'mockNumbers' => [],
'sessionAlerts' => false,
@@ -1755,28 +1756,7 @@ App::post('/v1/projects/:projectId/platforms')
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param(
'type',
null,
new WhiteList([
Platform::TYPE_WEB,
Platform::TYPE_FLUTTER_WEB,
Platform::TYPE_FLUTTER_IOS,
Platform::TYPE_FLUTTER_ANDROID,
Platform::TYPE_FLUTTER_LINUX,
Platform::TYPE_FLUTTER_MACOS,
Platform::TYPE_FLUTTER_WINDOWS,
Platform::TYPE_APPLE_IOS,
Platform::TYPE_APPLE_MACOS,
Platform::TYPE_APPLE_WATCHOS,
Platform::TYPE_APPLE_TVOS,
Platform::TYPE_ANDROID,
Platform::TYPE_UNITY,
Platform::TYPE_REACT_NATIVE_IOS,
Platform::TYPE_REACT_NATIVE_ANDROID,
], true),
'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.'
)
->param('type', null, new WhiteList([Platform::TYPE_WEB, Platform::TYPE_FLUTTER_WEB, Platform::TYPE_FLUTTER_IOS, Platform::TYPE_FLUTTER_ANDROID, Platform::TYPE_FLUTTER_LINUX, Platform::TYPE_FLUTTER_MACOS, Platform::TYPE_FLUTTER_WINDOWS, Platform::TYPE_APPLE_IOS, Platform::TYPE_APPLE_MACOS, Platform::TYPE_APPLE_WATCHOS, Platform::TYPE_APPLE_TVOS, Platform::TYPE_ANDROID, Platform::TYPE_UNITY, Platform::TYPE_REACT_NATIVE_IOS, Platform::TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
+22 -34
View File
@@ -28,9 +28,6 @@ use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Proofs\Password;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -474,9 +471,10 @@ App::post('/v1/teams/:teamId/memberships')
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], function (Document $project) {
if ($project->getId() === 'console') {
;
$roles = array_keys(Config::getParam('roles', []));
$roles = array_filter($roles, function ($role) {
return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]);
array_filter($roles, function ($role) {
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
@@ -495,9 +493,7 @@ App::post('/v1/teams/:teamId/memberships')
->inject('timelimit')
->inject('queueForStatsUsage')
->inject('plan')
->inject('proofForPassword')
->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, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
@@ -570,7 +566,6 @@ App::post('/v1/teams/:teamId/memberships')
try {
$userId = ID::unique();
$hash = $proofForPassword->hash($proofForPassword->generate());
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$permissions' => [
@@ -584,9 +579,9 @@ App::post('/v1/teams/:teamId/memberships')
'emailVerification' => false,
'status' => true,
// TODO: Set password empty?
'password' => $hash,
'hash' => $proofForPassword->getHash()->getName(),
'hashOptions' => $proofForPassword->getHash()->getOptions(),
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
/**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
@@ -618,7 +613,7 @@ App::post('/v1/teams/:teamId/memberships')
Query::equal('teamInternalId', [$team->getSequence()]),
]);
$secret = $proofForToken->generate();
$secret = Auth::tokenGenerator();
if ($membership->isEmpty()) {
$membershipId = ID::unique();
$membership = new Document([
@@ -638,7 +633,7 @@ App::post('/v1/teams/:teamId/memberships')
'invited' => DateTime::now(),
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
'confirm' => ($isPrivilegedUser || $isAppUser),
'secret' => $proofForToken->hash($secret),
'secret' => Auth::hash($secret),
'search' => implode(' ', [$membershipId, $invitee->getId()])
]);
@@ -651,7 +646,7 @@ App::post('/v1/teams/:teamId/memberships')
}
} elseif ($membership->getAttribute('confirm') === false) {
$membership->setAttribute('secret', $proofForToken->hash($secret));
$membership->setAttribute('secret', Auth::hash($secret));
$membership->setAttribute('invited', DateTime::now());
if ($isPrivilegedUser || $isAppUser) {
@@ -1075,7 +1070,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
array_filter($roles, function ($role) {
return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]);
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
@@ -1190,9 +1185,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('project')
->inject('geodb')
->inject('queueForEvents')
->inject('store')
->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, Document $project, Reader $geodb, Event $queueForEvents) {
$protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1211,7 +1204,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
}
if (!$proofForToken->verify($secret, $membership->getAttribute('secret'))) {
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
throw new Exception(Exception::TEAM_INVALID_SECRET);
}
@@ -1245,9 +1238,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
$secret = $proofForToken->generate();
$secret = Auth::tokenGenerator();
$session = new Document(array_merge([
'$id' => ID::unique(),
'$permissions' => [
@@ -1257,9 +1250,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'provider' => SESSION_PROVIDER_EMAIL,
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'factors' => ['email'],
@@ -1271,19 +1264,14 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Authorization::setRole(Role::user($userId)->toString());
$encoded = $store
->setProperty('id', $user->getId())
->setProperty('secret', $secret)
->encode();
if (!Config::getParam('domainVerification')) {
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
}
$response
->addCookie(
name: $store->getKey() . '_legacy',
value: $encoded,
name: Auth::$cookieName . '_legacy',
value: Auth::encodeSession($user->getId(), $secret),
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
@@ -1291,8 +1279,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
httponly: true
)
->addCookie(
name: $store->getKey(),
value: $encoded,
name: Auth::$cookieName,
value: Auth::encodeSession($user->getId(), $secret),
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
+43 -101
View File
@@ -1,6 +1,7 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\MFA\Type;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Auth\Validator\Password;
@@ -31,18 +32,6 @@ use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Hash;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Auth\Hashes\Bcrypt;
use Utopia\Auth\Hashes\MD5;
use Utopia\Auth\Hashes\PHPass;
use Utopia\Auth\Hashes\Plaintext;
use Utopia\Auth\Hashes\Scrypt;
use Utopia\Auth\Hashes\ScryptModified;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Password as ProofsPassword;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -71,9 +60,10 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
/** TODO: Remove function when we move to using utopia/platform */
function createUser(Hash $hash, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
{
$plaintextPassword = $password;
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
if (!empty($email)) {
@@ -107,18 +97,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
}
}
$hashedPassword = null;
if (!empty($password)) {
if ($hash instanceof Plaintext) { // Password was never hashed, hash it with the default hash
$defaultHash = new ProofsPassword();
$hashedPassword = $defaultHash->hash($password);
$hash = $defaultHash->getHash();
} else {
$hashedPassword = $password;
}
}
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
$user = new Document([
'$id' => $userId,
'$permissions' => [
@@ -132,11 +111,11 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
'phoneVerification' => false,
'status' => true,
'labels' => [],
'password' => $hashedPassword,
'passwordHistory' => is_null($hashedPassword) || $passwordHistory === 0 ? [] : [$hashedPassword],
'passwordUpdate' => (!empty($hashedPassword)) ? DateTime::now() : null,
'hash' => $hash->getName(),
'hashOptions' => $hash->getOptions(),
'password' => $password,
'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password],
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
@@ -147,7 +126,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
'search' => implode(' ', [$userId, $email, $phone, $name]),
]);
if ($hash instanceof Plaintext) {
if ($hash === 'plaintext') {
$hooks->trigger('passwordValidator', [$dbForProject, $project, $plaintextPassword, &$user, true]);
}
@@ -238,9 +217,7 @@ App::post('/v1/users')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$plaintext = new Plaintext();
$user = createUser($plaintext, $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
@@ -274,10 +251,7 @@ App::post('/v1/users/bcrypt')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$bcrypt = new Bcrypt();
$bcrypt->setCost(8); // Default cost
$user = createUser($bcrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -312,9 +286,7 @@ App::post('/v1/users/md5')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$md5 = new MD5();
$user = createUser($md5, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -349,13 +321,7 @@ App::post('/v1/users/argon2')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$argon2 = new Argon2();
$argon2
->setMemoryCost(2048)
->setTimeCost(4)
->setThreads(3);
$user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -391,12 +357,13 @@ App::post('/v1/users/sha')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$sha = new Sha();
$options = '{}';
if (!empty($passwordVersion)) {
$sha->setVersion($passwordVersion);
$options = '{"version":"' . $passwordVersion . '"}';
}
$user = createUser($sha, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -431,9 +398,7 @@ App::post('/v1/users/phpass')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$phpass = new PHPass();
$user = createUser($phpass, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -473,15 +438,15 @@ App::post('/v1/users/scrypt')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$scrypt = new Scrypt();
$scrypt
->setSalt($passwordSalt)
->setCpuCost($passwordCpu)
->setMemoryCost($passwordMemory)
->setParallelCost($passwordParallel)
->setLength($passwordLength);
$options = [
'salt' => $passwordSalt,
'costCpu' => $passwordCpu,
'costMemory' => $passwordMemory,
'costParallel' => $passwordParallel,
'length' => $passwordLength
];
$user = createUser($scrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -519,13 +484,7 @@ App::post('/v1/users/scrypt-modified')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$scryptModified = new ScryptModified();
$scryptModified
->setSalt($passwordSalt)
->setSaltSeparator($passwordSaltSeparator)
->setSignerKey($passwordSignerKey);
$user = createUser($scryptModified, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -1110,6 +1069,7 @@ App::get('/v1/users/identities')
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
@@ -1376,21 +1336,12 @@ App::patch('/v1/users/:userId/password')
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
// Create Argon2 hasher with default settings
$hasher = new Argon2();
$hasher
->setMemoryCost(2048)
->setTimeCost(4)
->setThreads(3);
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
$newPassword = $hasher->hash($password);
$hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions'));
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$history = $user->getAttribute('passwordHistory', []);
if ($historyLimit > 0) {
$validator = new PasswordHistory($history, $hash);
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
if (!$validator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED);
}
@@ -1403,8 +1354,8 @@ App::patch('/v1/users/:userId/password')
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
->setAttribute('passwordUpdate', DateTime::now())
->setAttribute('hash', $hasher->getName())
->setAttribute('hashOptions', $hasher->getOptions());
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
@@ -2217,19 +2168,17 @@ App::post('/v1/users/:userId/sessions')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->inject('store')
->inject('proofForToken')
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$secret = $proofForToken->generate();
$secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION);
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$session = new Document(array_merge(
@@ -2237,8 +2186,8 @@ App::post('/v1/users/:userId/sessions')
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'provider' => SESSION_PROVIDER_SERVER,
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
'provider' => Auth::SESSION_PROVIDER_SERVER,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'factors' => ['server'],
'ip' => $request->getIP(),
@@ -2262,13 +2211,8 @@ App::post('/v1/users/:userId/sessions')
$dbForProject->purgeCachedDocument('users', $user->getId());
$encoded = $store
->setProperty('id', $user->getId())
->setProperty('secret', $secret)
->encode();
$session
->setAttribute('secret', $encoded)
->setAttribute('secret', Auth::encodeSession($user->getId(), $secret))
->setAttribute('countryName', $countryName);
$queueForEvents
@@ -2303,7 +2247,7 @@ App::post('/v1/users/:userId/tokens')
))
->param('userId', '', new UID(), 'User ID.')
->param('length', 6, new Range(4, 128), 'Token length in characters. The default length is 6 characters', true)
->param('expire', TOKEN_EXPIRATION_GENERIC, new Range(60, TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
->param('expire', Auth::TOKEN_EXPIRATION_GENERIC, new Range(60, Auth::TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -2315,17 +2259,15 @@ App::post('/v1/users/:userId/tokens')
throw new Exception(Exception::USER_NOT_FOUND);
}
$proofForToken = new Token($length);
$proofForToken->setHash(new Sha());
$secret = $proofForToken->generate();
$secret = Auth::tokenGenerator($length);
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $expire));
$token = new Document([
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'type' => TOKEN_TYPE_GENERIC,
'secret' => $proofForToken->hash($secret),
'type' => Auth::TOKEN_TYPE_GENERIC,
'secret' => Auth::hash($secret),
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP()
+9 -22
View File
@@ -1185,29 +1185,17 @@ App::error()
$trace = $error->getTrace();
if (php_sapi_name() === 'cli') {
$logLevel = $code >= 500 || $code == 0 ? 'error' : 'warning';
$logPrefix = $code >= 500 || $code == 0 ? '[Error]' : '[Warning]';
Console::{$logLevel}($logPrefix . ' Timestamp: ' . date('c', time()));
Console::error('[Error] Timestamp: ' . date('c', time()));
if ($route) {
Console::{$logLevel}($logPrefix . ' Status Code: ' . $code);
Console::{$logLevel}($logPrefix . ' URL: ' . $route->getMethod() . ' ' . $route->getPath());
Console::error('[Error] Method: ' . $route->getMethod());
Console::error('[Error] URL: ' . $route->getPath());
}
Console::{$logLevel}($logPrefix . ' Type: ' . get_class($error));
Console::{$logLevel}($logPrefix . ' Message: ' . $message);
Console::{$logLevel}($logPrefix . ' File: ' . $file);
Console::{$logLevel}($logPrefix . ' Line: ' . $line);
Console::{$logLevel}($logPrefix . ' Trace:');
foreach ($trace as $index => $entry) {
$traceFile = $entry['file'] ?? 'unknown';
$traceLine = $entry['line'] ?? 0;
$traceFunction = $entry['function'] ?? 'unknown';
$traceClass = $entry['class'] ?? '';
$traceType = $entry['type'] ?? '';
Console::{$logLevel}(" #{$index} {$traceFile}({$traceLine}): {$traceClass}{$traceType}{$traceFunction}()");
}
Console::{$logLevel}('');
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $message);
Console::error('[Error] File: ' . $file);
Console::error('[Error] Line: ' . $line);
}
switch ($class) {
@@ -1265,7 +1253,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 its 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 (!Auth::isPrivilegedUser(Authorization::getRoles())) {
@@ -1367,7 +1355,6 @@ App::error()
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 416: // Error allowed publicly
case 422: // Error allowed publicly
case 429: // Error allowed publicly
case 451: // Error allowed publicly
case 501: // Error allowed publicly
+10 -69
View File
@@ -222,94 +222,39 @@ App::init()
->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) {
$route = $utopia->getRoute();
/**
* Handle user authentication and session validation.
*
* This function follows a series of steps to determine the appropriate user session
* based on cookies, headers, and JWT tokens.
*
* Process:
*
* Project & Role Validation:
* 1. Check if the project is empty. If so, throw an exception.
* 2. Get the roles configuration.
* 3. Determine the role for the user based on the user document.
* 4. Get the scopes for the role.
*
* API Key Authentication:
* 5. If there is an API key:
* - Verify no user session exists simultaneously
* - Check if key is expired
* - Set role and scopes from API key
* - Handle special app role case
* - For standard keys, update last accessed time
*
* User Activity:
* 6. If the project is not the console and user is not admin:
* - Update user's last activity timestamp
*
* Access Control:
* 7. Get the method from the route
* 8. Validate namespace permissions
* 9. Validate scope permissions
* 10. Check if user is blocked
*
* Security Checks:
* 11. Verify password status (check if reset required)
* 12. Validate MFA requirements:
* - Check if MFA is enabled
* - Verify email status
* - Verify phone status
* - Verify authenticator status
* 13. Handle Multi-Factor Authentication:
* - Check remaining required factors
* - Validate factor completion
* - Throw exception if factors incomplete
*/
// Step 1: Check if project is empty
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
// Step 2: Get roles configuration
$roles = Config::getParam('roles', []);
// Step 3: Determine role for user
// TODO get scopes from the identity instead of the user roles config. The identity will containn the scopes the user authorized for the access token.
$role = $user->isEmpty()
? Role::guests()->toString()
: Role::users()->toString();
// Step 4: Get scopes for the role
$scopes = $roles[$role]['scopes'];
// Step 5: API Key Authentication
// API Key authentication
if (!empty($apiKey)) {
// Verify no user session exists simultaneously
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if key is expired
if ($apiKey->isExpired()) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
}
// Set role and scopes from API key
$role = $apiKey->getRole();
$scopes = $apiKey->getScopes();
// Handle special app role case
if ($apiKey->getRole() === USER_ROLE_APPS) {
if ($apiKey->getRole() === Auth::USER_ROLE_APPS) {
// Disable authorization checks for API keys
Authorization::setDefaultStatus(false);
$user = new Document([
'$id' => '',
'status' => true,
'type' => ACTIVITY_TYPE_APP,
'type' => Auth::ACTIVITY_TYPE_APP,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $apiKey->getName(),
@@ -318,7 +263,6 @@ App::init()
$queueForAudits->setUser($user);
}
// For standard keys, update last accessed time
if ($apiKey->getType() === API_KEY_STANDARD) {
$dbKey = $project->find(
key: 'secret',
@@ -388,7 +332,7 @@ App::init()
Authorization::setRole($authRole);
}
// Step 6: Update project and user last activity
// Update project last activity
if (!$project->isEmpty() && $project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
@@ -397,6 +341,7 @@ App::init()
}
}
// Update user last activity
if (!empty($user->getId())) {
$accessedAt = $user->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
@@ -410,7 +355,6 @@ App::init()
}
}
// Steps 7-9: Access Control - Method, Namespace and Scope Validation
/**
* @var ?Method $method
*/
@@ -434,23 +378,21 @@ App::init()
}
}
// Step 9: Validate scope permissions
// Do now allow access if scope is not allowed
$allowed = (array)$route->getLabel('scope', 'none');
if (empty(\array_intersect($allowed, $scopes))) {
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scopes (' . \json_encode($allowed) . ')');
}
// Step 10: Check if user is blocked
// Do not allow access to blocked accounts
if (false === $user->getAttribute('status')) { // Account is blocked
throw new Exception(Exception::USER_BLOCKED);
}
// Step 11: Verify password status
if ($user->getAttribute('reset')) {
throw new Exception(Exception::USER_PASSWORD_RESET_REQUIRED);
}
// Step 12: Validate MFA requirements
$mfaEnabled = $user->getAttribute('mfa', false);
$hasVerifiedEmail = $user->getAttribute('emailVerification', false);
$hasVerifiedPhone = $user->getAttribute('phoneVerification', false);
@@ -458,7 +400,6 @@ App::init()
$hasMoreFactors = $hasVerifiedEmail || $hasVerifiedPhone || $hasVerifiedAuthenticator;
$minimumFactors = ($mfaEnabled && $hasMoreFactors) ? 2 : 1;
// Step 13: Handle Multi-Factor Authentication
if (!in_array('mfa', $route->getGroups())) {
if ($session && \count($session->getAttribute('factors', [])) < $minimumFactors) {
throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED);
@@ -585,7 +526,7 @@ App::init()
if (!$user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
}
@@ -825,7 +766,7 @@ App::shutdown()
if (!$user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
/**
@@ -839,7 +780,7 @@ App::shutdown()
$user = new Document([
'$id' => '',
'status' => true,
'type' => ACTIVITY_TYPE_GUEST,
'type' => Auth::ACTIVITY_TYPE_GUEST,
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => 'Guest',
+1 -1
View File
@@ -20,7 +20,7 @@ App::init()
$lastUpdate = $session->getAttribute('mfaUpdatedAt');
if (!empty($lastUpdate)) {
$now = DateTime::now();
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), Auth::MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
$isSessionFresh = DateTime::formatTz($maxAllowedDate) >= DateTime::formatTz($now);
}
+2 -81
View File
@@ -31,7 +31,6 @@ const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
const APP_LIMIT_DATABASE_BATCH = 100; // Default maximum batch size for database operations
const APP_LIMIT_DATABASE_TRANSACTION = 100; // Default maximum operations per transaction
const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
@@ -56,10 +55,6 @@ const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes
const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes
const APP_DATABASE_QUERY_MAX_VALUES = 500;
const APP_DATABASE_ENCRYPT_SIZE_MIN = 150;
const APP_DATABASE_TXN_TTL_MIN = 60; // 1 minute
const APP_DATABASE_TXN_TTL_MAX = 3600; // 1 hour
const APP_DATABASE_TXN_TTL_DEFAULT = 300; // 5 minutes
const APP_DATABASE_TXN_MAX_OPERATIONS = 100; // Maximum operations per transaction
const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_SITES = '/storage/sites';
const APP_STORAGE_FUNCTIONS = '/storage/functions';
@@ -90,73 +85,6 @@ const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
const APP_VCS_GITHUB_USERNAME = 'Appwrite';
const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled';
// User Roles
const USER_ROLE_ANY = 'any';
const USER_ROLE_GUESTS = 'guests';
const USER_ROLE_USERS = 'users';
const USER_ROLE_ADMIN = 'admin';
const USER_ROLE_DEVELOPER = 'developer';
const USER_ROLE_OWNER = 'owner';
const USER_ROLE_APPS = 'apps';
const USER_ROLE_SYSTEM = 'system';
/**
* Token Expiration times.
*/
const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */
const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */
const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */
/**
* Token Lengths.
*/
const TOKEN_LENGTH_MAGIC_URL = 64;
const TOKEN_LENGTH_VERIFICATION = 256;
const TOKEN_LENGTH_RECOVERY = 256;
const TOKEN_LENGTH_OAUTH2 = 64;
const TOKEN_LENGTH_SESSION = 256;
/**
* Token Types.
*/
const TOKEN_TYPE_LOGIN = 1; // Deprecated
const TOKEN_TYPE_VERIFICATION = 2;
const TOKEN_TYPE_RECOVERY = 3;
const TOKEN_TYPE_INVITE = 4;
const TOKEN_TYPE_MAGIC_URL = 5;
const TOKEN_TYPE_PHONE = 6;
const TOKEN_TYPE_OAUTH2 = 7;
const TOKEN_TYPE_GENERIC = 8;
const TOKEN_TYPE_EMAIL = 9; // OTP
/**
* Session Providers.
*/
const SESSION_PROVIDER_EMAIL = 'email';
const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
const SESSION_PROVIDER_PHONE = 'phone';
const SESSION_PROVIDER_OAUTH2 = 'oauth2';
const SESSION_PROVIDER_TOKEN = 'token';
const SESSION_PROVIDER_SERVER = 'server';
/**
* Activity associated with user or the app.
*/
const ACTIVITY_TYPE_APP = 'app';
const ACTIVITY_TYPE_USER = 'user';
const ACTIVITY_TYPE_GUEST = 'guest';
/**
* MFA
*/
const MFA_RECENT_DURATION = 1800; // 30 mins
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
@@ -176,11 +104,8 @@ const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_TRANSACTION = 'transaction';
const DELETE_TYPE_EXPIRED_TRANSACTIONS = 'expired_transactions';
const DELETE_TYPE_PROJECTS = 'projects';
const DELETE_TYPE_SITES = 'sites';
const DELETE_TYPE_FUNCTIONS = 'functions';
@@ -337,6 +262,7 @@ const METRIC_SITES_ID_INBOUND = 'sites.{siteInternalId}.inbound';
const METRIC_SITES_ID_OUTBOUND = 'sites.{siteInternalId}.outbound';
// Resource types
const RESOURCE_TYPE_PROJECTS = 'projects';
const RESOURCE_TYPE_FUNCTIONS = 'functions';
const RESOURCE_TYPE_SITES = 'sites';
@@ -346,15 +272,10 @@ const RESOURCE_TYPE_PROVIDERS = 'providers';
const RESOURCE_TYPE_TOPICS = 'topics';
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
const RESOURCE_TYPE_EXECUTIONS = 'executions';
// Resource types for Tokens
const TOKENS_RESOURCE_TYPE_FILES = 'files';
const TOKENS_RESOURCE_TYPE_SITES = 'sites';
const TOKENS_RESOURCE_TYPE_FUNCTIONS = 'functions';
const TOKENS_RESOURCE_TYPE_DATABASES = 'databases';
// Resource types for Schedules
const SCHEDULE_RESOURCE_TYPE_EXECUTION = 'execution';
const SCHEDULE_RESOURCE_TYPE_FUNCTION = 'function';
const SCHEDULE_RESOURCE_TYPE_MESSAGE = 'message';
-2
View File
@@ -255,8 +255,6 @@ Database::addFilter(
->find('variables', [
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::equal('resourceType', $resourceType),
Query::orderAsc('resourceType'),
Query::orderAsc(),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
+28 -10
View File
@@ -7,7 +7,6 @@ use Appwrite\PubSub\Adapter\Redis as PubSub;
use Appwrite\URL\URL as AppwriteURL;
use MaxMind\Db\Reader;
use PHPMailer\PHPMailer\PHPMailer;
use Swoole\Database\PDOProxy;
use Utopia\App;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\CLI\Console;
@@ -216,15 +215,34 @@ $register->set('pools', function () {
$resource = match ($dsnScheme) {
'mysql',
'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, [
\PDO::ATTR_TIMEOUT => 3, // Seconds
\PDO::ATTR_PERSISTENT => false,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES => true,
\PDO::ATTR_STRINGIFY_FETCHES => true
]);
});
$lastException = null;
$maxAttempts = 3;
for ($attempts = 1; $attempts <= $maxAttempts; $attempts++) {
try {
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, [
\PDO::ATTR_TIMEOUT => 3, // Seconds
\PDO::ATTR_PERSISTENT => false,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES => true,
\PDO::ATTR_STRINGIFY_FETCHES => true,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
]);
} catch (\Throwable $e) {
Console::warning("[Database] PDO connection attempt {$attempts} failed: " . $e->getMessage());
Console::warning("[Database] {$e->getTraceAsString()}");
$lastException = $e;
if ($attempts === $maxAttempts) {
throw $e;
}
// Wait extra 100ms per attempt
\usleep(100_000 * $attempts);
}
}
throw $lastException;
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new \Redis();
+54 -129
View File
@@ -4,7 +4,6 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Key;
use Appwrite\Databases\TransactionState;
use Appwrite\Event\Audit;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
@@ -24,16 +23,9 @@ use Appwrite\GraphQL\Schema;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code;
use Utopia\Auth\Proofs\Password;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -159,7 +151,7 @@ App::setResource('queueForMigrations', function (Publisher $publisher) {
App::setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
App::setResource('platforms', function (Request $request, Document $console, Document $project, Database $dbForPlatform) {
App::setResource('platforms', function (Request $request, Document $console, Document $project) {
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => ID::custom('platforms'),
'name' => 'Current Host',
@@ -198,128 +190,78 @@ App::setResource('platforms', function (Request $request, Document $console, Doc
], Document::SET_TYPE_APPEND);
}
$origin = \parse_url($request->getOrigin(), PHP_URL_HOST);
if (empty($origin)) {
$origin = \parse_url($request->getReferer(), PHP_URL_HOST);
}
// Safe if rule with same project ID exists
if (!empty($origin)) {
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
} else {
$rule = Authorization::skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$origin]),
Query::limit(1)
])
)[0] ?? new Document();
}
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) {
$project->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Platform::TYPE_WEB,
'name' => $origin,
'hostname' => $origin,
], Document::SET_TYPE_APPEND);
}
}
return [
...$console->getAttribute('platforms', []),
...$project->getAttribute('platforms', []),
];
}, ['request', 'console', 'project', 'dbForPlatform']);
}, ['request', 'console', 'project']);
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 ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var string $mode */
/** @var Utopia\Auth\Store $store */
/**
* Handles user authentication and session validation.
*
* This function follows a series of steps to determine the appropriate user session
* based on cookies, headers, and JWT tokens.
*
* Process:
* 1. Checks the cookie based on mode:
* - If in admin mode, uses console project id for key.
* - Otherwise, sets the key using the project ID
* 2. If no cookie is found, attempts to retrieve the fallback header `x-fallback-cookies`.
* - If this method is used, returns the header: `X-Debug-Fallback: true`.
* 3. Fetches the user document from the appropriate database based on the mode.
* 4. If the user document is empty or the session key cannot be verified, sets an empty user document.
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
* overwriting the previous value.
*/
Authorization::setDefaultStatus(true);
$store->setKey('a_session_' . $project->getId());
Auth::setCookieName('a_session_' . $project->getId());
if (APP_MODE_ADMIN === $mode) {
$store->setKey('a_session_' . $console->getId());
Auth::setCookieName('a_session_' . $console->getId());
}
$store->decode(
$session = Auth::decodeSession(
$request->getCookie(
$store->getKey(), // Get sessions
$request->getCookie($store->getKey() . '_legacy', '')
Auth::$cookieName, // Get sessions
$request->getCookie(Auth::$cookieName . '_legacy', '')
)
);
// Get session from header for SSR clients
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
if (empty($session['id']) && empty($session['secret'])) {
$sessionHeader = $request->getHeader('x-appwrite-session', '');
if (!empty($sessionHeader)) {
$store->decode($sessionHeader);
$session = Auth::decodeSession($sessionHeader);
}
}
// Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
if ($response) { // if in http context - add debug header
if ($response) {
$response->addHeader('X-Debug-Fallback', 'false');
}
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
if (empty($session['id']) && empty($session['secret'])) {
if ($response) {
$response->addHeader('X-Debug-Fallback', 'true');
}
$fallback = $request->getHeader('x-fallback-cookies', '');
$fallback = \json_decode($fallback, true);
$store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : ''));
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
}
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
$user = new Document([]);
if (APP_MODE_ADMIN === $mode) {
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
} else {
if ($project->isEmpty()) {
$user = new Document([]);
} else {
if (!empty($store->getProperty('id', ''))) {
if ($project->getId() === 'console') {
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
} else {
$user = $dbForProject->getDocument('users', $store->getProperty('id', ''));
}
if (!empty(Auth::$unique)) {
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} elseif (!$project->isEmpty()) {
if ($project->getId() === 'console') {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} else {
$user = $dbForProject->getDocument('users', Auth::$unique);
}
}
}
if (
$user->isEmpty() // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken)
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
) { // Validate user has valid login token
$user = new Document([]);
}
@@ -364,7 +306,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
$dbForPlatform->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']);
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
App::setResource('project', function ($dbForPlatform, $request, $console) {
/** @var Appwrite\Utopia\Request $request */
@@ -382,13 +324,13 @@ App::setResource('project', function ($dbForPlatform, $request, $console) {
return $project;
}, ['dbForPlatform', 'request', 'console']);
App::setResource('session', function (Document $user, Store $store, Token $proofForToken) {
App::setResource('session', function (Document $user) {
if ($user->isEmpty()) {
return;
}
$sessions = $user->getAttribute('sessions', []);
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken);
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
if (!$sessionId) {
return;
@@ -401,7 +343,7 @@ App::setResource('session', function (Document $user, Store $store, Token $proof
}
return;
}, ['user', 'store', 'proofForToken']);
}, ['user']);
App::setResource('console', function () {
return new Document(Config::getParam('console'));
@@ -433,7 +375,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int) $project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -486,7 +428,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int) $project->getSequence())
->setTenant((int)$project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -516,7 +458,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return function (?Document $project = null) use ($pools, $cache, &$database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int) $project->getSequence());
$database->setTenant((int)$project->getSequence());
return $database;
}
@@ -531,7 +473,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int) $project->getSequence());
$database->setTenant((int)$project->getSequence());
}
return $database;
@@ -559,7 +501,7 @@ App::setResource('redis', function () {
$pass = System::getEnv('_APP_REDIS_PASS', '');
$redis = new \Redis();
@$redis->pconnect($host, (int) $port);
@$redis->pconnect($host, (int)$port);
if ($pass) {
$redis->auth($pass);
}
@@ -772,7 +714,7 @@ App::setResource('schema', function ($utopia, $dbForProject) {
// NOTE: `params` and `urls` are not used internally in the `Schema::build` function below!
$params = [
'list' => function (string $databaseId, string $collectionId, array $args) {
return ['queries' => $args['queries']];
return [ 'queries' => $args['queries']];
},
'create' => function (string $databaseId, string $collectionId, array $args) {
$id = $args['id'] ?? 'unique()';
@@ -975,37 +917,6 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
return Key::decode($project, $key);
}, ['request', 'project']);
App::setResource('store', function (): Store {
return new Store();
});
App::setResource('proofForPassword', function (): Password {
$hash = new Argon2();
$hash
->setMemoryCost(2048)
->setTimeCost(4)
->setThreads(3);
$password = new Password();
$password
->setHash($hash);
return $password;
});
App::setResource('proofForToken', function (): Token {
$token = new Token();
$token->setHash(new Sha());
return $token;
});
App::setResource('proofForCode', function (): Code {
$code = new Code();
$code->setHash(new Sha());
return $code;
});
App::setResource('executor', fn () => new Executor());
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
@@ -1052,7 +963,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
}
$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());
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
}
@@ -1094,6 +1005,24 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef
return $referrer;
}
// Safe if rule with same project ID exists
if (!empty($origin)) {
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
} else {
$rule = Authorization::skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$origin]),
Query::limit(1)
])
)[0] ?? new Document();
}
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) {
return $referrer;
}
}
// Unsafe; Localhost is always safe for ease of local development
$origin = 'localhost';
$protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME);
@@ -1101,7 +1030,3 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
return $referrer;
}, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']);
App::setResource('transactionState', function (Database $dbForProject) {
return new TransactionState($dbForProject);
}, ['dbForProject']);
+8 -39
View File
@@ -16,9 +16,6 @@ use Swoole\Timer;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -607,18 +604,11 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$code = 500;
}
$message = $th->getMessage();
// sanitize 0 && 5xx errors
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
$message = 'Error: Server Error';
}
$response = [
'type' => 'error',
'data' => [
'code' => $code,
'message' => $message
'message' => $th->getMessage()
]
];
@@ -681,24 +671,15 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
}
$store = new Store();
$session = Auth::decodeSession($message['data']['session']);
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
$store->decode($message['data']['session']);
$user = $database->getDocument('users', $store->getProperty('id', ''));
/**
* TODO:
* Moving forward, we should try to use our dependency injection container
* to inject the proof for token.
* This way we will have one source of truth for the proof for token.
*/
$proofForToken = new Token();
$proofForToken->setHash(new Sha());
$user = $database->getDocument('users', Auth::$unique);
if (
empty($user->getId()) // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) // Validate user has valid login token
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token
) {
// cookie not valid
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
@@ -724,23 +705,11 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
}
} catch (Throwable $th) {
$code = $th->getCode();
if (!is_int($code)) {
$code = 500;
}
$message = $th->getMessage();
// sanitize 0 && 5xx errors
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
$message = 'Error: Server Error';
}
$response = [
'type' => 'error',
'data' => [
'code' => $code,
'message' => $message
'code' => $th->getCode(),
'message' => $th->getMessage()
]
];
+2 -3
View File
@@ -46,14 +46,13 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.19.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/auth": "0.4.*",
"utopia-php/abuse": "1.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "1.*",
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "2.*",
"utopia-php/database": "1.*",
"utopia-php/detector": "0.1.*",
"utopia-php/domains": "0.8.*",
"utopia-php/dns": "0.3.*",
@@ -63,7 +62,7 @@
"utopia-php/image": "0.8.*",
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.19.*",
"utopia-php/messaging": "0.18.*",
"utopia-php/migration": "1.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
Generated
+149 -213
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9658991ad6520dad5807d7e1ed1e9bd4",
"content-hash": "7553e976312b0423cc31544abb91caec",
"packages": [
{
"name": "adhocore/jwt",
@@ -756,16 +756,16 @@
},
{
"name": "google/protobuf",
"version": "v4.33.0",
"version": "v4.32.1",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d"
"reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d",
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb",
"reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb",
"shasum": ""
},
"require": {
@@ -794,9 +794,9 @@
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0"
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.1"
},
"time": "2025-10-15T20:10:28+00:00"
"time": "2025-09-14T05:14:52+00:00"
},
{
"name": "league/csv",
@@ -1159,20 +1159,20 @@
},
{
"name": "open-telemetry/api",
"version": "1.7.0",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/api.git",
"reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522"
"reference": "7692075f486c14d8cfd37fba98a08a5667f089e5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/610b79ad9d6d97e8368bcb6c4d42394fbb87b522",
"reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7692075f486c14d8cfd37fba98a08a5667f089e5",
"reference": "7692075f486c14d8cfd37fba98a08a5667f089e5",
"shasum": ""
},
"require": {
"open-telemetry/context": "^1.4",
"open-telemetry/context": "^1.0",
"php": "^8.1",
"psr/log": "^1.1|^2.0|^3.0",
"symfony/polyfill-php82": "^1.26"
@@ -1188,7 +1188,7 @@
]
},
"branch-alias": {
"dev-main": "1.7.x-dev"
"dev-main": "1.4.x-dev"
}
},
"autoload": {
@@ -1225,20 +1225,20 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-10-02T23:44:28+00:00"
"time": "2025-08-07T23:07:38+00:00"
},
{
"name": "open-telemetry/context",
"version": "1.4.0",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/context.git",
"reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf"
"reference": "438f71812242db3f196fb4c717c6f92cbc819be6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf",
"reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf",
"url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6",
"reference": "438f71812242db3f196fb4c717c6f92cbc819be6",
"shasum": ""
},
"require": {
@@ -1284,7 +1284,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-09-19T00:05:49+00:00"
"time": "2025-08-13T01:12:00+00:00"
},
{
"name": "open-telemetry/exporter-otlp",
@@ -1352,16 +1352,16 @@
},
{
"name": "open-telemetry/gen-otlp-protobuf",
"version": "1.8.0",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git",
"reference": "673af5b06545b513466081884b47ef15a536edde"
"reference": "585bafddd4ae6565de154610b10a787a455c9ba0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/673af5b06545b513466081884b47ef15a536edde",
"reference": "673af5b06545b513466081884b47ef15a536edde",
"url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/585bafddd4ae6565de154610b10a787a455c9ba0",
"reference": "585bafddd4ae6565de154610b10a787a455c9ba0",
"shasum": ""
},
"require": {
@@ -1411,27 +1411,27 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-09-17T23:10:12+00:00"
"time": "2025-01-15T23:07:07+00:00"
},
{
"name": "open-telemetry/sdk",
"version": "1.9.0",
"version": "1.7.1",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/sdk.git",
"reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e"
"reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e",
"reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e",
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/52690d4b37ae4f091af773eef3c238ed2bc0aa06",
"reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06",
"shasum": ""
},
"require": {
"ext-json": "*",
"nyholm/psr7-server": "^1.1",
"open-telemetry/api": "^1.7",
"open-telemetry/context": "^1.4",
"open-telemetry/api": "^1.4",
"open-telemetry/context": "^1.0",
"open-telemetry/sem-conv": "^1.0",
"php": "^8.1",
"php-http/discovery": "^1.14",
@@ -1465,7 +1465,7 @@
]
},
"branch-alias": {
"dev-main": "1.9.x-dev"
"dev-main": "1.0.x-dev"
}
},
"autoload": {
@@ -1508,7 +1508,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-10-02T23:44:28+00:00"
"time": "2025-09-05T07:17:06+00:00"
},
{
"name": "open-telemetry/sem-conv",
@@ -1569,16 +1569,16 @@
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.8.2",
"version": "v2.7.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226"
"reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/e30811f7bc69e4b5b6d5783e712c06c8eabf0226",
"reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105",
"reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105",
"shasum": ""
},
"require": {
@@ -1632,7 +1632,7 @@
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"time": "2025-09-24T15:12:37+00:00"
"time": "2024-05-08T12:18:48+00:00"
},
{
"name": "paragonie/random_compat",
@@ -1927,16 +1927,16 @@
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.47",
"version": "3.0.46",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d"
"reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d",
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
"reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
"shasum": ""
},
"require": {
@@ -2017,7 +2017,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.47"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.46"
},
"funding": [
{
@@ -2033,7 +2033,7 @@
"type": "tidelift"
}
],
"time": "2025-10-06T01:07:24+00:00"
"time": "2025-06-26T16:29:55+00:00"
},
{
"name": "psr/container",
@@ -2596,16 +2596,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.3.4",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62"
"reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62",
"reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62",
"url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019",
"reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019",
"shasum": ""
},
"require": {
@@ -2672,7 +2672,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.3.4"
"source": "https://github.com/symfony/http-client/tree/v7.3.3"
},
"funding": [
{
@@ -2692,7 +2692,7 @@
"type": "tidelift"
}
],
"time": "2025-09-11T10:12:26+00:00"
"time": "2025-08-27T07:45:05+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -3293,16 +3293,16 @@
},
{
"name": "utopia-php/abuse",
"version": "1.0.1",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
"reference": "cd591568791556d246d901d6aaf9935ab02c3f9a"
"reference": "c5e2232033b507a07f72180dc56d37e1872ee7be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/cd591568791556d246d901d6aaf9935ab02c3f9a",
"reference": "cd591568791556d246d901d6aaf9935ab02c3f9a",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/c5e2232033b507a07f72180dc56d37e1872ee7be",
"reference": "c5e2232033b507a07f72180dc56d37e1872ee7be",
"shasum": ""
},
"require": {
@@ -3310,7 +3310,7 @@
"ext-pdo": "*",
"ext-redis": "*",
"php": ">=8.0",
"utopia-php/database": "2.*"
"utopia-php/database": "1.*"
},
"require-dev": {
"laravel/pint": "1.*",
@@ -3338,9 +3338,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
"source": "https://github.com/utopia-php/abuse/tree/1.0.1"
"source": "https://github.com/utopia-php/abuse/tree/1.0.0"
},
"time": "2025-09-04T12:46:54+00:00"
"time": "2025-08-13T09:12:54+00:00"
},
{
"name": "utopia-php/analytics",
@@ -3390,21 +3390,21 @@
},
{
"name": "utopia-php/audit",
"version": "1.0.1",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22"
"reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/5ef26d6a2ab2db7bb86288a1a6ef970307b46f22",
"reference": "5ef26d6a2ab2db7bb86288a1a6ef970307b46f22",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/c0ed75f4d068f1f6c2e7149a909490d4214e72bb",
"reference": "c0ed75f4d068f1f6c2e7149a909490d4214e72bb",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/database": "2.*"
"utopia-php/database": "1.*"
},
"require-dev": {
"laravel/pint": "1.*",
@@ -3431,64 +3431,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/1.0.1"
"source": "https://github.com/utopia-php/audit/tree/1.0.0"
},
"time": "2025-09-04T12:46:43+00:00"
},
{
"name": "utopia-php/auth",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/auth.git",
"reference": "02415e1a89cdbc14e3e16a7856ecf7f868869449"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/auth/zipball/02415e1a89cdbc14e3e16a7856ecf7f868869449",
"reference": "02415e1a89cdbc14e3e16a7856ecf7f868869449",
"shasum": ""
},
"require": {
"ext-hash": "*",
"ext-scrypt": "*",
"ext-sodium": "*",
"php": ">=8.0"
},
"require-dev": {
"laravel/pint": "1.2.*",
"phpstan/phpstan": "1.9.x-dev",
"phpunit/phpunit": "^9.3",
"vimeo/psalm": "4.0.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\Auth\\": "src/Auth"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Utopia PHP",
"email": "team@appwrite.io"
}
],
"description": "A simple PHP authentication library",
"keywords": [
"Authentication",
"auth",
"php",
"security"
],
"support": {
"issues": "https://github.com/utopia-php/auth/issues",
"source": "https://github.com/utopia-php/auth/tree/0.4.0"
},
"time": "2025-04-29T19:29:28+00:00"
"time": "2025-08-13T09:09:00+00:00"
},
{
"name": "utopia-php/cache",
@@ -3690,16 +3635,16 @@
},
{
"name": "utopia-php/database",
"version": "2.3.2",
"version": "1.4.9",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "35c978ddd20b649d119296094686d3cc9fcf161f"
"reference": "066e2bda7b728bb843776db3640737d7350ba035"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/35c978ddd20b649d119296094686d3cc9fcf161f",
"reference": "35c978ddd20b649d119296094686d3cc9fcf161f",
"url": "https://api.github.com/repos/utopia-php/database/zipball/066e2bda7b728bb843776db3640737d7350ba035",
"reference": "066e2bda7b728bb843776db3640737d7350ba035",
"shasum": ""
},
"require": {
@@ -3740,9 +3685,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/2.3.2"
"source": "https://github.com/utopia-php/database/tree/1.4.9"
},
"time": "2025-10-07T03:09:32+00:00"
"time": "2025-09-16T13:31:52+00:00"
},
{
"name": "utopia-php/detector",
@@ -3847,16 +3792,16 @@
},
{
"name": "utopia-php/domains",
"version": "0.8.4",
"version": "0.8.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/domains.git",
"reference": "316a76b0fcbc7f82e40be466c22c19acb243ee22"
"reference": "650463d2a1525273eb03223c48da9fb1a768bbf7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/domains/zipball/316a76b0fcbc7f82e40be466c22c19acb243ee22",
"reference": "316a76b0fcbc7f82e40be466c22c19acb243ee22",
"url": "https://api.github.com/repos/utopia-php/domains/zipball/650463d2a1525273eb03223c48da9fb1a768bbf7",
"reference": "650463d2a1525273eb03223c48da9fb1a768bbf7",
"shasum": ""
},
"require": {
@@ -3902,9 +3847,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/domains/issues",
"source": "https://github.com/utopia-php/domains/tree/0.8.4"
"source": "https://github.com/utopia-php/domains/tree/0.8.0"
},
"time": "2025-10-17T11:31:56+00:00"
"time": "2025-05-16T10:03:59+00:00"
},
{
"name": "utopia-php/dsn",
@@ -3994,16 +3939,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.33.28",
"version": "0.33.27",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "5aaa94d406577b0059ad28c78022606890dc6de0"
"reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/5aaa94d406577b0059ad28c78022606890dc6de0",
"reference": "5aaa94d406577b0059ad28c78022606890dc6de0",
"url": "https://api.github.com/repos/utopia-php/http/zipball/d9d10a895e85c8c7675220347cc6109db9d3bd37",
"reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37",
"shasum": ""
},
"require": {
@@ -4035,9 +3980,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.33.28"
"source": "https://github.com/utopia-php/http/tree/0.33.27"
},
"time": "2025-09-25T10:44:24+00:00"
"time": "2025-09-07T18:40:53+00:00"
},
{
"name": "utopia-php/image",
@@ -4191,16 +4136,16 @@
},
{
"name": "utopia-php/messaging",
"version": "0.19.0",
"version": "0.18.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/messaging.git",
"reference": "0b866d54e70c792a3c4f5ca9c12a6d358a31f4b8"
"reference": "0d364edacf4d4867964c7e17f653031dd39394bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/0b866d54e70c792a3c4f5ca9c12a6d358a31f4b8",
"reference": "0b866d54e70c792a3c4f5ca9c12a6d358a31f4b8",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/0d364edacf4d4867964c7e17f653031dd39394bf",
"reference": "0d364edacf4d4867964c7e17f653031dd39394bf",
"shasum": ""
},
"require": {
@@ -4236,22 +4181,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/messaging/issues",
"source": "https://github.com/utopia-php/messaging/tree/0.19.0"
"source": "https://github.com/utopia-php/messaging/tree/0.18.2"
},
"time": "2025-10-14T11:46:49+00:00"
"time": "2025-07-21T18:27:03+00:00"
},
{
"name": "utopia-php/migration",
"version": "1.1.0",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038"
"reference": "c42935a6a4ee3701c68d24244e82ecb39e945ec4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/6fb6f8f032cd34c3c65728a55d494adeac2ff038",
"reference": "6fb6f8f032cd34c3c65728a55d494adeac2ff038",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/c42935a6a4ee3701c68d24244e82ecb39e945ec4",
"reference": "c42935a6a4ee3701c68d24244e82ecb39e945ec4",
"shasum": ""
},
"require": {
@@ -4259,7 +4204,7 @@
"ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.1",
"utopia-php/database": "2.*",
"utopia-php/database": "1.*",
"utopia-php/dsn": "0.2.*",
"utopia-php/framework": "0.33.*",
"utopia-php/storage": "0.18.*"
@@ -4292,9 +4237,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/1.1.0"
"source": "https://github.com/utopia-php/migration/tree/1.1.1"
},
"time": "2025-09-10T05:45:30+00:00"
"time": "2025-09-10T06:17:20+00:00"
},
{
"name": "utopia-php/orchestration",
@@ -4621,16 +4566,16 @@
},
{
"name": "utopia-php/storage",
"version": "0.18.14",
"version": "0.18.13",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "4f14ec952c6f4006dd0613e55bbf7631814fbc00"
"reference": "3d8ce53ae042173bf230445e996056c5f65ded22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/4f14ec952c6f4006dd0613e55bbf7631814fbc00",
"reference": "4f14ec952c6f4006dd0613e55bbf7631814fbc00",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/3d8ce53ae042173bf230445e996056c5f65ded22",
"reference": "3d8ce53ae042173bf230445e996056c5f65ded22",
"shasum": ""
},
"require": {
@@ -4673,9 +4618,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.18.14"
"source": "https://github.com/utopia-php/storage/tree/0.18.13"
},
"time": "2025-10-07T10:21:47+00:00"
"time": "2025-05-26T13:10:35+00:00"
},
{
"name": "utopia-php/swoole",
@@ -5059,16 +5004,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.4.4",
"version": "1.3.4",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49"
"reference": "d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49",
"reference": "a20b20cfd70a1879f0d0fb2b4f669aa5ed836c49",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac",
"reference": "d3b420dced42f1eec1f6d0aa98b7bbf8de4042ac",
"shasum": ""
},
"require": {
@@ -5104,9 +5049,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/1.4.4"
"source": "https://github.com/appwrite/sdk-generator/tree/1.3.4"
},
"time": "2025-10-13T09:20:49+00:00"
"time": "2025-09-08T11:56:04+00:00"
},
{
"name": "doctrine/annotations",
@@ -5182,7 +5127,6 @@
"issues": "https://github.com/doctrine/annotations/issues",
"source": "https://github.com/doctrine/annotations/tree/2.0.2"
},
"abandoned": true,
"time": "2024-09-05T10:17:24+00:00"
},
{
@@ -5334,16 +5278,16 @@
},
{
"name": "laravel/pint",
"version": "v1.25.1",
"version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9"
"reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9",
"reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9",
"url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a",
"reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a",
"shasum": ""
},
"require": {
@@ -5354,9 +5298,9 @@
"php": "^8.2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.87.2",
"illuminate/view": "^11.46.0",
"larastan/larastan": "^3.7.1",
"friendsofphp/php-cs-fixer": "^3.82.2",
"illuminate/view": "^11.45.1",
"larastan/larastan": "^3.5.0",
"laravel-zero/framework": "^11.45.0",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^2.3.1",
@@ -5367,6 +5311,9 @@
],
"type": "project",
"autoload": {
"files": [
"overrides/Runner/Parallel/ProcessFactory.php"
],
"psr-4": {
"App\\": "app/",
"Database\\Seeders\\": "database/seeders/",
@@ -5396,7 +5343,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2025-09-19T02:57:12+00:00"
"time": "2025-07-10T18:09:32+00:00"
},
{
"name": "matthiasmullie/minify",
@@ -6286,16 +6233,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.6.29",
"version": "9.6.27",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3"
"reference": "0a9aa4440b6a9528cf360071502628d717af3e0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3",
"reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0a9aa4440b6a9528cf360071502628d717af3e0a",
"reference": "0a9aa4440b6a9528cf360071502628d717af3e0a",
"shasum": ""
},
"require": {
@@ -6320,7 +6267,7 @@
"sebastian/comparator": "^4.0.9",
"sebastian/diff": "^4.0.6",
"sebastian/environment": "^5.1.5",
"sebastian/exporter": "^4.0.8",
"sebastian/exporter": "^4.0.6",
"sebastian/global-state": "^5.0.8",
"sebastian/object-enumerator": "^4.0.4",
"sebastian/resource-operations": "^3.0.4",
@@ -6369,7 +6316,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.27"
},
"funding": [
{
@@ -6393,7 +6340,7 @@
"type": "tidelift"
}
],
"time": "2025-09-24T06:29:11+00:00"
"time": "2025-09-14T06:18:03+00:00"
},
{
"name": "psr/cache",
@@ -6885,16 +6832,16 @@
},
{
"name": "sebastian/exporter",
"version": "4.0.8",
"version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c"
"reference": "78c00df8f170e02473b682df15bfcdacc3d32d72"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c",
"reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72",
"reference": "78c00df8f170e02473b682df15bfcdacc3d32d72",
"shasum": ""
},
"require": {
@@ -6950,27 +6897,15 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8"
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
"type": "tidelift"
}
],
"time": "2025-09-24T06:03:27+00:00"
"time": "2024-03-02T06:33:00+00:00"
},
{
"name": "sebastian/global-state",
@@ -7553,16 +7488,16 @@
},
{
"name": "symfony/console",
"version": "v7.3.4",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db"
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"shasum": ""
},
"require": {
@@ -7627,7 +7562,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.3.4"
"source": "https://github.com/symfony/console/tree/v7.3.3"
},
"funding": [
{
@@ -7647,7 +7582,7 @@
"type": "tidelift"
}
],
"time": "2025-09-22T15:31:00+00:00"
"time": "2025-08-25T06:35:40+00:00"
},
{
"name": "symfony/filesystem",
@@ -8190,16 +8125,16 @@
},
{
"name": "symfony/process",
"version": "v7.3.4",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
"url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
"shasum": ""
},
"require": {
@@ -8231,7 +8166,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.3.4"
"source": "https://github.com/symfony/process/tree/v7.3.3"
},
"funding": [
{
@@ -8251,20 +8186,20 @@
"type": "tidelift"
}
],
"time": "2025-09-11T10:12:26+00:00"
"time": "2025-08-18T09:42:54+00:00"
},
{
"name": "symfony/string",
"version": "v7.3.4",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "f96476035142921000338bad71e5247fbc138872"
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
"reference": "f96476035142921000338bad71e5247fbc138872",
"url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"shasum": ""
},
"require": {
@@ -8279,6 +8214,7 @@
},
"require-dev": {
"symfony/emoji": "^7.1",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0",
@@ -8321,7 +8257,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.3.4"
"source": "https://github.com/symfony/string/tree/v7.3.3"
},
"funding": [
{
@@ -8341,7 +8277,7 @@
"type": "tidelift"
}
],
"time": "2025-09-11T14:36:48+00:00"
"time": "2025-08-25T06:35:40+00:00"
},
{
"name": "textalk/websocket",
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createEmailVerification(
"https://example.com", // url
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,23 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.updateEmailVerification(
"<USER_ID>", // userId
"<SECRET>", // secret
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -20,7 +20,6 @@ databases.createDocument(
"isAdmin" to false
), // data
listOf("read("any")"), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,33 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Databases databases = new Databases(client);
databases.createOperations(
"<TRANSACTION_ID>", // transactionId
listOf(
{
"action": "create",
"databaseId": "<DATABASE_ID>",
"collectionId": "<COLLECTION_ID>",
"documentId": "<DOCUMENT_ID>",
"data": {
"name": "Walter O'Brien"
}
}
), // operations (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Databases databases = new Databases(client);
databases.createTransaction(
60, // ttl (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -15,7 +15,6 @@ databases.decrementDocumentAttribute(
"", // attribute
0, // value (optional)
0, // min (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -12,7 +12,6 @@ databases.deleteDocument(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Databases databases = new Databases(client);
databases.deleteTransaction(
"<TRANSACTION_ID>", // transactionId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -13,7 +13,6 @@ databases.getDocument(
"<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId
listOf(), // queries (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Databases databases = new Databases(client);
databases.getTransaction(
"<TRANSACTION_ID>", // transactionId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -15,7 +15,6 @@ databases.incrementDocumentAttribute(
"", // attribute
0, // value (optional)
0, // max (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -12,7 +12,6 @@ databases.listDocuments(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
listOf(), // queries (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Databases databases = new Databases(client);
databases.listTransactions(
listOf(), // queries (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -14,7 +14,6 @@ databases.updateDocument(
"<DOCUMENT_ID>", // documentId
mapOf( "a" to "b" ), // data (optional)
listOf("read("any")"), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,24 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Databases databases = new Databases(client);
databases.updateTransaction(
"<TRANSACTION_ID>", // transactionId
false, // commit (optional)
false, // rollback (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -14,7 +14,6 @@ databases.upsertDocument(
"<DOCUMENT_ID>", // documentId
mapOf( "a" to "b" ), // data
listOf("read("any")"), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,33 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
TablesDB tablesDB = new TablesDB(client);
tablesDB.createOperations(
"<TRANSACTION_ID>", // transactionId
listOf(
{
"action": "create",
"databaseId": "<DATABASE_ID>",
"tableId": "<TABLE_ID>",
"rowId": "<ROW_ID>",
"data": {
"name": "Walter O'Brien"
}
}
), // operations (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -20,7 +20,6 @@ tablesDB.createRow(
"isAdmin" to false
), // data
listOf("read("any")"), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
TablesDB tablesDB = new TablesDB(client);
tablesDB.createTransaction(
60, // ttl (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -15,7 +15,6 @@ tablesDB.decrementRowColumn(
"", // column
0, // value (optional)
0, // min (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -12,7 +12,6 @@ tablesDB.deleteRow(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
"<ROW_ID>", // rowId
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
TablesDB tablesDB = new TablesDB(client);
tablesDB.deleteTransaction(
"<TRANSACTION_ID>", // transactionId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -13,7 +13,6 @@ tablesDB.getRow(
"<TABLE_ID>", // tableId
"<ROW_ID>", // rowId
listOf(), // queries (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
TablesDB tablesDB = new TablesDB(client);
tablesDB.getTransaction(
"<TRANSACTION_ID>", // transactionId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -15,7 +15,6 @@ tablesDB.incrementRowColumn(
"", // column
0, // value (optional)
0, // max (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -12,7 +12,6 @@ tablesDB.listRows(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
listOf(), // queries (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
TablesDB tablesDB = new TablesDB(client);
tablesDB.listTransactions(
listOf(), // queries (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -14,7 +14,6 @@ tablesDB.updateRow(
"<ROW_ID>", // rowId
mapOf( "a" to "b" ), // data (optional)
listOf("read("any")"), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,24 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
TablesDB tablesDB = new TablesDB(client);
tablesDB.updateTransaction(
"<TRANSACTION_ID>", // transactionId
false, // commit (optional)
false, // rollback (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -14,7 +14,6 @@ tablesDB.upsertRow(
"<ROW_ID>", // rowId
mapOf( "a" to "b" ), // data (optional)
listOf("read("any")"), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Account
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val account = Account(client)
val result = account.createEmailVerification(
url = "https://example.com",
)
@@ -1,14 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Account
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val account = Account(client)
val result = account.updateEmailVerification(
userId = "<USER_ID>",
secret = "<SECRET>",
)
@@ -20,5 +20,4 @@ val result = databases.createDocument(
"isAdmin" to false
),
permissions = listOf("read("any")"), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,24 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val databases = Databases(client)
val result = databases.createOperations(
transactionId = "<TRANSACTION_ID>",
operations = listOf(
{
"action": "create",
"databaseId": "<DATABASE_ID>",
"collectionId": "<COLLECTION_ID>",
"documentId": "<DOCUMENT_ID>",
"data": {
"name": "Walter O'Brien"
}
}
), // (optional)
)
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val databases = Databases(client)
val result = databases.createTransaction(
ttl = 60, // (optional)
)
@@ -15,5 +15,4 @@ val result = databases.decrementDocumentAttribute(
attribute = "",
value = 0, // (optional)
min = 0, // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -12,5 +12,4 @@ val result = databases.deleteDocument(
databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>",
documentId = "<DOCUMENT_ID>",
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val databases = Databases(client)
val result = databases.deleteTransaction(
transactionId = "<TRANSACTION_ID>",
)
@@ -13,5 +13,4 @@ val result = databases.getDocument(
collectionId = "<COLLECTION_ID>",
documentId = "<DOCUMENT_ID>",
queries = listOf(), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val databases = Databases(client)
val result = databases.getTransaction(
transactionId = "<TRANSACTION_ID>",
)
@@ -15,5 +15,4 @@ val result = databases.incrementDocumentAttribute(
attribute = "",
value = 0, // (optional)
max = 0, // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -12,5 +12,4 @@ val result = databases.listDocuments(
databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>",
queries = listOf(), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val databases = Databases(client)
val result = databases.listTransactions(
queries = listOf(), // (optional)
)
@@ -14,5 +14,4 @@ val result = databases.updateDocument(
documentId = "<DOCUMENT_ID>",
data = mapOf( "a" to "b" ), // (optional)
permissions = listOf("read("any")"), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,15 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val databases = Databases(client)
val result = databases.updateTransaction(
transactionId = "<TRANSACTION_ID>",
commit = false, // (optional)
rollback = false, // (optional)
)
@@ -14,5 +14,4 @@ val result = databases.upsertDocument(
documentId = "<DOCUMENT_ID>",
data = mapOf( "a" to "b" ),
permissions = listOf("read("any")"), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,24 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.TablesDB
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val tablesDB = TablesDB(client)
val result = tablesDB.createOperations(
transactionId = "<TRANSACTION_ID>",
operations = listOf(
{
"action": "create",
"databaseId": "<DATABASE_ID>",
"tableId": "<TABLE_ID>",
"rowId": "<ROW_ID>",
"data": {
"name": "Walter O'Brien"
}
}
), // (optional)
)
@@ -20,5 +20,4 @@ val result = tablesDB.createRow(
"isAdmin" to false
),
permissions = listOf("read("any")"), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.TablesDB
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val tablesDB = TablesDB(client)
val result = tablesDB.createTransaction(
ttl = 60, // (optional)
)
@@ -15,5 +15,4 @@ val result = tablesDB.decrementRowColumn(
column = "",
value = 0, // (optional)
min = 0, // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -12,5 +12,4 @@ val result = tablesDB.deleteRow(
databaseId = "<DATABASE_ID>",
tableId = "<TABLE_ID>",
rowId = "<ROW_ID>",
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.TablesDB
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val tablesDB = TablesDB(client)
val result = tablesDB.deleteTransaction(
transactionId = "<TRANSACTION_ID>",
)
@@ -13,5 +13,4 @@ val result = tablesDB.getRow(
tableId = "<TABLE_ID>",
rowId = "<ROW_ID>",
queries = listOf(), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.TablesDB
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val tablesDB = TablesDB(client)
val result = tablesDB.getTransaction(
transactionId = "<TRANSACTION_ID>",
)
@@ -15,5 +15,4 @@ val result = tablesDB.incrementRowColumn(
column = "",
value = 0, // (optional)
max = 0, // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -12,5 +12,4 @@ val result = tablesDB.listRows(
databaseId = "<DATABASE_ID>",
tableId = "<TABLE_ID>",
queries = listOf(), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)
@@ -1,13 +0,0 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.TablesDB
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val tablesDB = TablesDB(client)
val result = tablesDB.listTransactions(
queries = listOf(), // (optional)
)
@@ -14,5 +14,4 @@ val result = tablesDB.updateRow(
rowId = "<ROW_ID>",
data = mapOf( "a" to "b" ), // (optional)
permissions = listOf("read("any")"), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)

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