mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
4020 lines
158 KiB
PHP
4020 lines
158 KiB
PHP
<?php
|
|
|
|
namespace Tests\E2E\Services\Account;
|
|
|
|
use Appwrite\Tests\Retry;
|
|
use Tests\E2E\Client;
|
|
use Tests\E2E\Scopes\ProjectCustom;
|
|
use Tests\E2E\Scopes\Scope;
|
|
use Tests\E2E\Scopes\SideClient;
|
|
use Utopia\Database\DateTime;
|
|
use Utopia\Database\Helpers\ID;
|
|
use Utopia\Database\Query;
|
|
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
|
|
|
use function sleep;
|
|
|
|
class AccountCustomClientTest extends Scope
|
|
{
|
|
use AccountBase;
|
|
use ProjectCustom;
|
|
use SideClient;
|
|
|
|
/**
|
|
* Static cache for account data across tests
|
|
*/
|
|
private static array $accountData = [];
|
|
private static array $sessionData = [];
|
|
private static array $updatedNameData = [];
|
|
private static array $updatedPasswordData = [];
|
|
private static array $updatedEmailData = [];
|
|
private static array $updatedPrefsData = [];
|
|
private static array $verificationData = [];
|
|
private static array $verifiedData = [];
|
|
private static array $recoveryData = [];
|
|
private static array $phoneData = [];
|
|
private static array $phoneSessionData = [];
|
|
private static array $phonePasswordData = [];
|
|
private static array $phoneUpdatedData = [];
|
|
private static array $phoneVerificationData = [];
|
|
private static array $magicUrlData = [];
|
|
private static array $magicUrlSessionData = [];
|
|
|
|
/**
|
|
* Helper to set up an account with session
|
|
*/
|
|
protected function setupAccountWithSession(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$sessionData[$cacheKey])) {
|
|
return self::$sessionData[$cacheKey];
|
|
}
|
|
|
|
// First create an account
|
|
$accountData = $this->setupAccount();
|
|
|
|
$email = $accountData['email'];
|
|
$password = $accountData['password'];
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$sessionId = $response['body']['$id'];
|
|
$session = $response['cookies']['a_session_' . $projectId];
|
|
|
|
self::$sessionData[$cacheKey] = array_merge($accountData, [
|
|
'sessionId' => $sessionId,
|
|
'session' => $session,
|
|
]);
|
|
|
|
return self::$sessionData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to create a fresh account with session (bypasses cache).
|
|
* Use this when you need a predictable session/log count for testing.
|
|
*/
|
|
protected function createFreshAccountWithSession(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
|
|
// Use more entropy to avoid collisions in parallel test execution
|
|
$email = uniqid('', true) . getmypid() . bin2hex(random_bytes(4)) . '@localhost.test';
|
|
$password = 'password';
|
|
$name = 'User Name';
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'name' => $name,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$id = $response['body']['$id'];
|
|
|
|
// Create session
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$sessionId = $response['body']['$id'];
|
|
$session = $response['cookies']['a_session_' . $projectId];
|
|
|
|
return [
|
|
'id' => $id,
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'name' => $name,
|
|
'sessionId' => $sessionId,
|
|
'session' => $session,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up a basic account
|
|
*/
|
|
protected function setupAccount(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$accountData[$cacheKey])) {
|
|
return self::$accountData[$cacheKey];
|
|
}
|
|
|
|
$email = uniqid() . 'user@localhost.test';
|
|
$password = 'password';
|
|
$name = 'User Name';
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'name' => $name,
|
|
]);
|
|
|
|
$id = $response['body']['$id'];
|
|
|
|
self::$accountData[$cacheKey] = [
|
|
'id' => $id,
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'name' => $name,
|
|
];
|
|
|
|
return self::$accountData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up account with updated name
|
|
*/
|
|
protected function setupAccountWithUpdatedName(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$updatedNameData[$cacheKey])) {
|
|
return self::$updatedNameData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupAccountWithSession();
|
|
$session = $data['session'];
|
|
$newName = 'Lorem';
|
|
|
|
$this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]), [
|
|
'name' => $newName
|
|
]);
|
|
|
|
self::$updatedNameData[$cacheKey] = array_merge($data, ['name' => $newName]);
|
|
|
|
return self::$updatedNameData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up account with updated password
|
|
*/
|
|
protected function setupAccountWithUpdatedPassword(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$updatedPasswordData[$cacheKey])) {
|
|
return self::$updatedPasswordData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupAccountWithUpdatedName();
|
|
$session = $data['session'];
|
|
$password = $data['password'];
|
|
|
|
$this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]), [
|
|
'password' => 'new-password',
|
|
'oldPassword' => $password,
|
|
]);
|
|
|
|
self::$updatedPasswordData[$cacheKey] = array_merge($data, ['password' => 'new-password']);
|
|
|
|
return self::$updatedPasswordData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up account with updated email
|
|
*/
|
|
protected function setupAccountWithUpdatedEmail(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$updatedEmailData[$cacheKey])) {
|
|
return self::$updatedEmailData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupAccountWithUpdatedPassword();
|
|
$session = $data['session'];
|
|
$newEmail = uniqid() . 'new@localhost.test';
|
|
|
|
$this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]), [
|
|
'email' => $newEmail,
|
|
'password' => 'new-password',
|
|
]);
|
|
|
|
self::$updatedEmailData[$cacheKey] = array_merge($data, ['email' => $newEmail]);
|
|
|
|
return self::$updatedEmailData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up account with updated prefs
|
|
*/
|
|
protected function setupAccountWithUpdatedPrefs(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$updatedPrefsData[$cacheKey])) {
|
|
return self::$updatedPrefsData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupAccountWithUpdatedEmail();
|
|
$session = $data['session'];
|
|
|
|
$this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]), [
|
|
'prefs' => [
|
|
'prefKey1' => 'prefValue1',
|
|
'prefKey2' => 'prefValue2',
|
|
]
|
|
]);
|
|
|
|
self::$updatedPrefsData[$cacheKey] = $data;
|
|
|
|
return self::$updatedPrefsData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up account with verification created
|
|
*/
|
|
protected function setupAccountWithVerification(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$verificationData[$cacheKey])) {
|
|
return self::$verificationData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupAccountWithUpdatedPrefs();
|
|
$email = $data['email'];
|
|
$session = $data['session'];
|
|
|
|
$this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]), [
|
|
'url' => 'http://localhost/verification',
|
|
]);
|
|
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
$tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
|
|
$verification = $tokens['secret'];
|
|
|
|
self::$verificationData[$cacheKey] = array_merge($data, ['verification' => $verification]);
|
|
|
|
return self::$verificationData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up account with verified email
|
|
*/
|
|
protected function setupAccountWithVerifiedEmail(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$verifiedData[$cacheKey])) {
|
|
return self::$verifiedData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupAccountWithVerification();
|
|
$id = $data['id'];
|
|
$session = $data['session'];
|
|
$verification = $data['verification'];
|
|
|
|
$this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $verification,
|
|
]);
|
|
|
|
self::$verifiedData[$cacheKey] = $data;
|
|
|
|
return self::$verifiedData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up account with recovery token
|
|
*/
|
|
protected function setupAccountWithRecovery(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$recoveryData[$cacheKey])) {
|
|
return self::$recoveryData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupAccountWithVerifiedEmail();
|
|
$email = $data['email'];
|
|
|
|
$this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'email' => $email,
|
|
'url' => 'http://localhost/recovery',
|
|
]);
|
|
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
$tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
|
|
$recovery = $tokens['secret'];
|
|
|
|
self::$recoveryData[$cacheKey] = array_merge($data, ['recovery' => $recovery]);
|
|
|
|
return self::$recoveryData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up phone account
|
|
*/
|
|
protected function setupPhoneAccount(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$phoneData[$cacheKey])) {
|
|
return self::$phoneData[$cacheKey];
|
|
}
|
|
|
|
// Ensure phone auth is enabled (may have been disabled by testPhoneVerification in parallel)
|
|
$this->ensurePhoneAuthEnabled();
|
|
|
|
// Use a truly unique phone number for parallel test safety
|
|
// Combine microtime, PID, and random digits to avoid collisions across parallel processes
|
|
$number = '+1' . substr(str_replace('.', '', microtime(true)) . getmypid() . random_int(100, 999), -9);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'phone' => $number,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$userId = $response['body']['userId'];
|
|
|
|
$smsRequest = $this->getLastRequestForProject(
|
|
$projectId,
|
|
Scope::REQUEST_TYPE_SMS,
|
|
[
|
|
'header_X-Username' => 'username',
|
|
'header_X-Key' => 'password',
|
|
'method' => 'POST',
|
|
],
|
|
probe: function (array $request) use ($number) {
|
|
$this->assertEquals($number, $request['data']['to'] ?? null);
|
|
}
|
|
);
|
|
|
|
$this->assertNotEmpty($smsRequest, 'SMS request not found for phone number: ' . $number);
|
|
|
|
self::$phoneData[$cacheKey] = [
|
|
'token' => $smsRequest['data']['message'],
|
|
'id' => $userId,
|
|
'number' => $number,
|
|
];
|
|
|
|
return self::$phoneData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up phone session
|
|
*/
|
|
protected function setupPhoneSession(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$phoneSessionData[$cacheKey])) {
|
|
return self::$phoneSessionData[$cacheKey];
|
|
}
|
|
|
|
// Try up to 3 times with fresh phone accounts if session creation fails
|
|
$maxRetries = 3;
|
|
$lastError = '';
|
|
|
|
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
|
|
// Force fresh phone account on retry
|
|
if ($attempt > 0) {
|
|
unset(self::$phoneData[$cacheKey]);
|
|
\usleep(500000); // 500ms between retries
|
|
}
|
|
|
|
$data = $this->setupPhoneAccount();
|
|
$id = $data['id'];
|
|
// Extract OTP token - try the raw message first, then first word
|
|
$rawMessage = $data['token'];
|
|
$token = \trim($rawMessage);
|
|
if (\str_contains($token, ' ')) {
|
|
$token = \explode(' ', $token)[0];
|
|
}
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $token,
|
|
]);
|
|
|
|
if ($response['headers']['status-code'] === 201) {
|
|
$session = $response['cookies']['a_session_' . $projectId];
|
|
self::$phoneSessionData[$cacheKey] = array_merge($data, ['session' => $session]);
|
|
return self::$phoneSessionData[$cacheKey];
|
|
}
|
|
|
|
$lastError = 'Attempt ' . ($attempt + 1) . ': Phone session creation failed (status ' . $response['headers']['status-code'] . '). Token: "' . $token . '", Raw message: "' . $rawMessage . '", UserId: ' . $id;
|
|
}
|
|
|
|
$this->fail($lastError);
|
|
}
|
|
|
|
/**
|
|
* Helper to set up phone account converted to password
|
|
*/
|
|
protected function setupPhoneConvertedToPassword(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$phonePasswordData[$cacheKey])) {
|
|
return self::$phonePasswordData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupPhoneSession();
|
|
$session = $data['session'];
|
|
$email = uniqid() . 'new@localhost.test';
|
|
$password = 'new-password';
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
// Re-login with email to get a fresh session after credential change
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$session = $response['cookies']['a_session_' . $projectId];
|
|
|
|
self::$phonePasswordData[$cacheKey] = array_merge($data, [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'session' => $session,
|
|
]);
|
|
|
|
return self::$phonePasswordData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up phone account with updated phone
|
|
*/
|
|
protected function setupPhoneUpdated(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$phoneUpdatedData[$cacheKey])) {
|
|
return self::$phoneUpdatedData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupPhoneConvertedToPassword();
|
|
$session = $data['session'];
|
|
// Use a truly unique phone number to avoid target conflicts across parallel test runs
|
|
$newPhone = '+456' . substr(str_replace('.', '', microtime(true)) . getmypid() . random_int(100, 999), -8);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]), [
|
|
'phone' => $newPhone,
|
|
'password' => 'new-password'
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
self::$phoneUpdatedData[$cacheKey] = array_merge($data, ['phone' => $newPhone]);
|
|
|
|
return self::$phoneUpdatedData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up phone verification
|
|
*/
|
|
protected function setupPhoneVerification(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$phoneVerificationData[$cacheKey])) {
|
|
return self::$phoneVerificationData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupPhoneUpdated();
|
|
$session = $data['session'];
|
|
$phone = $data['phone'];
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'cookie' => 'a_session_' . $projectId . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$tokenCreatedAt = $response['body']['$createdAt'];
|
|
|
|
$smsRequest = $this->getLastRequestForProject(
|
|
$projectId,
|
|
Scope::REQUEST_TYPE_SMS,
|
|
[
|
|
'header_X-Username' => 'username',
|
|
'header_X-Key' => 'password',
|
|
'method' => 'POST',
|
|
],
|
|
probe: function (array $request) use ($tokenCreatedAt, $phone) {
|
|
if (!empty($phone)) {
|
|
$this->assertEquals($phone, $request['data']['to'] ?? null);
|
|
}
|
|
$tokenRecievedAt = $request['time'];
|
|
$this->assertGreaterThan($tokenCreatedAt, $tokenRecievedAt);
|
|
}
|
|
);
|
|
|
|
$this->assertNotEmpty($smsRequest, 'SMS request not found for phone verification');
|
|
|
|
self::$phoneVerificationData[$cacheKey] = array_merge($data, [
|
|
'token' => \substr($smsRequest['data']['message'], 0, 6)
|
|
]);
|
|
|
|
return self::$phoneVerificationData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up magic URL account
|
|
*/
|
|
protected function setupMagicUrl(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$magicUrlData[$cacheKey])) {
|
|
return self::$magicUrlData[$cacheKey];
|
|
}
|
|
|
|
$email = \time() . 'user@appwrite.io';
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
]);
|
|
|
|
$userId = $response['body']['userId'];
|
|
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
$token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64);
|
|
|
|
self::$magicUrlData[$cacheKey] = [
|
|
'token' => $token,
|
|
'id' => $userId,
|
|
'email' => $email,
|
|
];
|
|
|
|
return self::$magicUrlData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to set up magic URL session
|
|
*/
|
|
protected function setupMagicUrlSession(): array
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$cacheKey = $projectId;
|
|
|
|
if (!empty(self::$magicUrlSessionData[$cacheKey])) {
|
|
return self::$magicUrlSessionData[$cacheKey];
|
|
}
|
|
|
|
$data = $this->setupMagicUrl();
|
|
$id = $data['id'];
|
|
$token = $data['token'];
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $token,
|
|
]);
|
|
|
|
$sessionId = $response['body']['$id'];
|
|
$session = $response['cookies']['a_session_' . $projectId];
|
|
|
|
self::$magicUrlSessionData[$cacheKey] = array_merge($data, [
|
|
'sessionId' => $sessionId,
|
|
'session' => $session,
|
|
]);
|
|
|
|
return self::$magicUrlSessionData[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Helper to create an anonymous session (returns new session each time)
|
|
*/
|
|
protected function createAnonymousSession(): string
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]);
|
|
|
|
return $response['cookies']['a_session_' . $projectId];
|
|
}
|
|
|
|
/**
|
|
* Helper to delete any existing user with the given email.
|
|
* Used to prevent parallel test conflicts when tests share
|
|
* hardcoded emails (e.g. from mock OAuth providers).
|
|
*/
|
|
protected function deleteUserByEmail(string $email): void
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
$apiKey = $this->getProject()['apiKey'];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/users', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'x-appwrite-key' => $apiKey,
|
|
], [
|
|
'queries' => [
|
|
Query::equal('email', [$email])->toString(),
|
|
],
|
|
]);
|
|
|
|
if ($response['headers']['status-code'] === 200) {
|
|
foreach ($response['body']['users'] ?? [] as $user) {
|
|
$this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
'x-appwrite-key' => $apiKey,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper to ensure phone auth is enabled for the project.
|
|
* Needed because testPhoneVerification disables it and other
|
|
* parallel tests may need it.
|
|
*/
|
|
protected function ensurePhoneAuthEnabled(): void
|
|
{
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'status' => true,
|
|
]);
|
|
}
|
|
|
|
public function testCreateAccountSession(): void
|
|
{
|
|
$data = $this->setupAccount();
|
|
$email = $data['email'];
|
|
$password = $data['password'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotFalse(\DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $response['body']['expire']));
|
|
|
|
$sessionId = $response['body']['$id'];
|
|
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
// apiKey is only available in custom client test
|
|
$apiKey = $this->getProject()['apiKey'];
|
|
if (!empty($apiKey)) {
|
|
$userId = $response['body']['userId'];
|
|
$response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $apiKey,
|
|
]));
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertArrayHasKey('accessedAt', $response['body']);
|
|
$this->assertNotEmpty($response['body']['accessedAt']);
|
|
}
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertNotFalse(\DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $response['body']['expire']));
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email . 'x',
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password . 'x',
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => '',
|
|
'password' => '',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testGetAccount(): void
|
|
{
|
|
$data = $this->setupAccountWithSession();
|
|
$email = $data['email'];
|
|
$name = $data['name'];
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $email);
|
|
$this->assertEquals($response['body']['name'], $name);
|
|
$this->assertArrayHasKey('accessedAt', $response['body']);
|
|
$this->assertNotEmpty($response['body']['accessedAt']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', [
|
|
'content-type' => 'application/json',
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session . 'xx',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testGetAccountPrefs(): void
|
|
{
|
|
$data = $this->setupAccountWithSession();
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertEmpty($response['body']);
|
|
$this->assertCount(0, $response['body']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testGetAccountSessions(): void
|
|
{
|
|
// Use fresh account for predictable session count
|
|
$data = $this->createFreshAccountWithSession();
|
|
$session = $data['session'];
|
|
$sessionId = $data['sessionId'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals(1, $response['body']['total']);
|
|
$this->assertEquals($sessionId, $response['body']['sessions'][0]['$id']);
|
|
$this->assertEmpty($response['body']['sessions'][0]['secret']);
|
|
|
|
$this->assertEquals('Windows', $response['body']['sessions'][0]['osName']);
|
|
$this->assertEquals('WIN', $response['body']['sessions'][0]['osCode']);
|
|
$this->assertEquals('10', $response['body']['sessions'][0]['osVersion']);
|
|
|
|
$this->assertEquals('browser', $response['body']['sessions'][0]['clientType']);
|
|
$this->assertEquals('Chrome', $response['body']['sessions'][0]['clientName']);
|
|
$this->assertEquals('CH', $response['body']['sessions'][0]['clientCode']);
|
|
$this->assertEquals('70.0', $response['body']['sessions'][0]['clientVersion']);
|
|
$this->assertEquals('Blink', $response['body']['sessions'][0]['clientEngine']);
|
|
$this->assertEquals('desktop', $response['body']['sessions'][0]['deviceName']);
|
|
$this->assertEquals('', $response['body']['sessions'][0]['deviceBrand']);
|
|
$this->assertEquals('', $response['body']['sessions'][0]['deviceModel']);
|
|
$this->assertEquals($response['body']['sessions'][0]['ip'], filter_var($response['body']['sessions'][0]['ip'], FILTER_VALIDATE_IP));
|
|
|
|
$this->assertEquals('--', $response['body']['sessions'][0]['countryCode']);
|
|
$this->assertEquals('Unknown', $response['body']['sessions'][0]['countryName']);
|
|
|
|
$this->assertEquals(true, $response['body']['sessions'][0]['current']);
|
|
|
|
$this->assertNotFalse(\DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $response['body']['sessions'][0]['expire']));
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testGetAccountLogs(): void
|
|
{
|
|
// Use fresh account for predictable log count
|
|
$data = $this->createFreshAccountWithSession();
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']['logs']);
|
|
$this->assertNotEmpty($response['body']['logs']);
|
|
// Fresh account: session.create is always logged. user.create audit may or may not
|
|
// be present depending on async audit processing timing.
|
|
$logCount = count($response['body']['logs']);
|
|
$this->assertContains($logCount, [1, 2]);
|
|
$this->assertIsNumeric($response['body']['total']);
|
|
|
|
// Check session.create log (logs[0] - most recent)
|
|
$this->assertEquals('Windows', $response['body']['logs'][0]['osName']);
|
|
$this->assertEquals('WIN', $response['body']['logs'][0]['osCode']);
|
|
$this->assertEquals('10', $response['body']['logs'][0]['osVersion']);
|
|
|
|
$this->assertEquals('browser', $response['body']['logs'][0]['clientType']);
|
|
$this->assertEquals('Chrome', $response['body']['logs'][0]['clientName']);
|
|
$this->assertEquals('CH', $response['body']['logs'][0]['clientCode']);
|
|
$this->assertEquals('70.0', $response['body']['logs'][0]['clientVersion']);
|
|
$this->assertEquals('Blink', $response['body']['logs'][0]['clientEngine']);
|
|
|
|
$this->assertEquals('desktop', $response['body']['logs'][0]['deviceName']);
|
|
$this->assertEquals('', $response['body']['logs'][0]['deviceBrand']);
|
|
$this->assertEquals('', $response['body']['logs'][0]['deviceModel']);
|
|
$this->assertEquals(filter_var($response['body']['logs'][0]['ip'], FILTER_VALIDATE_IP), $response['body']['logs'][0]['ip']);
|
|
|
|
$this->assertEquals('--', $response['body']['logs'][0]['countryCode']);
|
|
$this->assertEquals('Unknown', $response['body']['logs'][0]['countryName']);
|
|
|
|
if ($logCount === 2) {
|
|
// Check user.create log (logs[1] - oldest)
|
|
$this->assertEquals('user.create', $response['body']['logs'][1]['event']);
|
|
$this->assertEquals(filter_var($response['body']['logs'][1]['ip'], FILTER_VALIDATE_IP), $response['body']['logs'][1]['ip']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['logs'][1]['time']));
|
|
}
|
|
|
|
$responseLimit = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'queries' => [
|
|
Query::limit(1)->toString()
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals(200, $responseLimit['headers']['status-code']);
|
|
$this->assertIsArray($responseLimit['body']['logs']);
|
|
$this->assertNotEmpty($responseLimit['body']['logs']);
|
|
$this->assertCount(1, $responseLimit['body']['logs']);
|
|
$this->assertIsNumeric($responseLimit['body']['total']);
|
|
|
|
$this->assertEquals($response['body']['logs'][0], $responseLimit['body']['logs'][0]);
|
|
|
|
$responseOffset = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'queries' => [
|
|
Query::offset(1)->toString()
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals($responseOffset['headers']['status-code'], 200);
|
|
$this->assertIsArray($responseOffset['body']['logs']);
|
|
// With offset(1), remaining logs = logCount - 1
|
|
$this->assertCount($logCount - 1, $responseOffset['body']['logs']);
|
|
$this->assertIsNumeric($responseOffset['body']['total']);
|
|
|
|
if ($logCount === 2) {
|
|
$this->assertEquals($response['body']['logs'][1], $responseOffset['body']['logs'][0]);
|
|
}
|
|
|
|
$responseLimitOffset = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'queries' => [
|
|
Query::offset(1)->toString(),
|
|
Query::limit(1)->toString()
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals(200, $responseLimitOffset['headers']['status-code']);
|
|
$this->assertIsArray($responseLimitOffset['body']['logs']);
|
|
// With offset(1)+limit(1), remaining logs = min(1, logCount - 1)
|
|
$this->assertCount(min(1, $logCount - 1), $responseLimitOffset['body']['logs']);
|
|
$this->assertIsNumeric($responseLimitOffset['body']['total']);
|
|
|
|
if ($logCount === 2) {
|
|
$this->assertEquals($response['body']['logs'][1], $responseLimitOffset['body']['logs'][0]);
|
|
}
|
|
|
|
/**
|
|
* Test for total=false
|
|
*/
|
|
$logsWithIncludeTotalFalse = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'total' => false
|
|
]);
|
|
|
|
$this->assertEquals(200, $logsWithIncludeTotalFalse['headers']['status-code']);
|
|
$this->assertIsArray($logsWithIncludeTotalFalse['body']);
|
|
$this->assertIsArray($logsWithIncludeTotalFalse['body']['logs']);
|
|
$this->assertIsInt($logsWithIncludeTotalFalse['body']['total']);
|
|
$this->assertEquals(0, $logsWithIncludeTotalFalse['body']['total']);
|
|
$this->assertGreaterThan(0, count($logsWithIncludeTotalFalse['body']['logs']));
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
// TODO Add tests for OAuth2 session creation
|
|
|
|
public function testUpdateAccountName(): void
|
|
{
|
|
$data = $this->setupAccountWithSession();
|
|
$email = $data['email'];
|
|
$session = $data['session'];
|
|
$newName = 'Lorem';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'name' => $newName
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $email);
|
|
$this->assertEquals($response['body']['name'], $newName);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), []);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'name' => 'ocSRq1d3QphHivJyUmYY7WMnrxyjdk5YvVwcDqx2zS0coxESN8RmsQwLWw5Whnf0WbVohuFWTRAaoKgCOO0Y0M7LwgFnZmi8881Y72222222222222222222222222222'
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
#[Retry(count: 1)]
|
|
public function testUpdateAccountPassword(): void
|
|
{
|
|
$data = $this->setupAccountWithUpdatedName();
|
|
$email = $data['email'];
|
|
$password = $data['password'];
|
|
$session = $data['session'];
|
|
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
usleep(500000);
|
|
}
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$allSessions = array_map(fn ($sessionDetails) => $sessionDetails['$id'], $response['body']['sessions']);
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'password' => 'new-password',
|
|
'oldPassword' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $email);
|
|
|
|
$currentSessionId = $data['sessionId'] ?? '';
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals(1, $response['body']['total']);
|
|
// checking the current session or not
|
|
$this->assertEquals($currentSessionId, $response['body']['sessions'][0]['$id']);
|
|
$this->assertTrue($response['body']['sessions'][0]['current']);
|
|
|
|
// checking for all non active sessions are cleared
|
|
foreach ($allSessions as $sessionId) {
|
|
if ($currentSessionId === $sessionId) {
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
} else {
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
}
|
|
}
|
|
|
|
$newPassword = 'new-password';
|
|
// updating the invalidateSession to false to check sessions are not invalidated
|
|
$this->updateProjectinvalidateSessionsProperty(false);
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $newPassword,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
usleep(500000);
|
|
}
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$allSessions = array_map(fn ($sessionDetails) => $sessionDetails['$id'], $response['body']['sessions']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'password' => $newPassword,
|
|
'oldPassword' => $newPassword,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
foreach ($allSessions as $sessionId) {
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, headers: array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
}
|
|
|
|
// setting invalidateSession to true to check the sessions are cleared or not
|
|
$this->updateProjectinvalidateSessionsProperty(true);
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'password' => $newPassword,
|
|
'oldPassword' => $newPassword,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$allSessions = array_map(fn ($sessionDetails) => $sessionDetails['$id'], $response['body']['sessions']);
|
|
|
|
foreach ($allSessions as $sessionId) {
|
|
if ($currentSessionId !== $sessionId) {
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
}
|
|
}
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $newPassword,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), []);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Existing user tries to update password by passing wrong old password -> SHOULD FAIL
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'password' => 'new-password',
|
|
'oldPassword' => $password,
|
|
]);
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Existing user tries to update password without passing old password -> SHOULD FAIL
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'password' => 'new-password'
|
|
]);
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateAccountEmail(): void
|
|
{
|
|
$data = $this->setupAccountWithUpdatedPassword();
|
|
$newEmail = uniqid() . 'new@localhost.test';
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'email' => $newEmail,
|
|
'password' => 'new-password',
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $newEmail);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), []);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
// Test if we can create a new account with the old email
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? ''
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $data['email'],
|
|
'password' => $data['password'],
|
|
'name' => $data['name'],
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $data['email']);
|
|
$this->assertEquals($response['body']['name'], $data['name']);
|
|
}
|
|
|
|
public function testUpdateAccountPrefs(): void
|
|
{
|
|
$data = $this->setupAccountWithUpdatedEmail();
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'prefs' => [
|
|
'prefKey1' => 'prefValue1',
|
|
'prefKey2' => 'prefValue2',
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertEquals('prefValue1', $response['body']['prefs']['prefKey1']);
|
|
$this->assertEquals('prefValue2', $response['body']['prefs']['prefKey2']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'prefs' => '{}'
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'prefs' => '[]'
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'prefs' => '{"test": "value"}'
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Prefs size exceeded
|
|
*/
|
|
$prefsObject = ["longValue" => str_repeat("🍰", 100000)];
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'prefs' => $prefsObject
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
// Now let's test the same thing, but with normal symbol instead of multi-byte cake emoji
|
|
$prefsObject = ["longValue" => str_repeat("-", 100000)];
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'prefs' => $prefsObject
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateAccountVerification(): void
|
|
{
|
|
$data = $this->setupAccountWithUpdatedPrefs();
|
|
$email = $data['email'];
|
|
$name = $data['name'];
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
|
|
]), [
|
|
'url' => 'http://localhost/verification',
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
|
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
|
|
$this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
|
|
$this->assertEquals($name, $lastEmail['to'][0]['name']);
|
|
$this->assertEquals('Account Verification for ' . $this->getProject()['name'], $lastEmail['subject']);
|
|
$this->assertStringContainsStringIgnoringCase('Verify your email to activate your ' . $this->getProject()['name'] . ' account.', $lastEmail['text']);
|
|
|
|
$tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
|
|
$verification = $tokens['secret'];
|
|
$expectedExpire = DateTime::formatTz($response['body']['expire']);
|
|
$this->assertEquals($expectedExpire, $tokens['expire']);
|
|
|
|
// Secret check
|
|
$this->assertArrayHasKey('secret', $tokens);
|
|
$this->assertNotEmpty($tokens['secret']);
|
|
|
|
// User ID check
|
|
$this->assertArrayHasKey('userId', $tokens);
|
|
$this->assertNotEmpty($tokens['userId']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'url' => 'localhost/verification',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'url' => 'http://remotehost/verification',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateAccountVerification(): void
|
|
{
|
|
$data = $this->setupAccountWithVerification();
|
|
$id = $data['id'];
|
|
$session = $data['session'];
|
|
$verification = $data['verification'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $verification,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'userId' => ID::custom('ewewe'),
|
|
'secret' => $verification,
|
|
]);
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => 'sdasdasdasd',
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testDeleteAccountSession(): void
|
|
{
|
|
$data = $this->setupAccountWithVerifiedEmail();
|
|
$email = $data['email'];
|
|
$password = $data['password'];
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$sessionNewId = $response['body']['$id'];
|
|
$sessionNew = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_DELETE, '/account/sessions/' . $sessionNewId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
|
|
]));
|
|
|
|
$this->assertEquals(204, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testDeleteAccountSessionCurrent(): void
|
|
{
|
|
$data = $this->setupAccountWithVerifiedEmail();
|
|
$email = $data['email'];
|
|
$password = $data['password'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$sessionNew = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]);
|
|
|
|
$this->assertEquals(204, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testDeleteAccountSessions(): void
|
|
{
|
|
$data = $this->setupAccountWithVerifiedEmail();
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_DELETE, '/account/sessions', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(204, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
}
|
|
|
|
public function testCreateAccountRecovery(): void
|
|
{
|
|
$data = $this->setupAccountWithVerifiedEmail();
|
|
$email = $data['email'];
|
|
$name = $data['name'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'url' => 'http://localhost/recovery',
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
|
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
|
|
$this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
|
|
$this->assertEquals($name, $lastEmail['to'][0]['name']);
|
|
$this->assertEquals('Password Reset for ' . $this->getProject()['name'], $lastEmail['subject']);
|
|
$this->assertStringContainsStringIgnoringCase('Reset your ' . $this->getProject()['name'] . ' password using the link.', $lastEmail['text']);
|
|
|
|
|
|
$tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
|
|
|
|
// Secret check
|
|
$this->assertArrayHasKey('secret', $tokens);
|
|
$this->assertNotEmpty($tokens['secret']);
|
|
$this->assertNotFalse($response['body']['secret']);
|
|
|
|
// User ID check
|
|
$this->assertArrayHasKey('userId', $tokens);
|
|
$this->assertNotEmpty($tokens['userId']);
|
|
$this->assertNotFalse($response['body']['userId']);
|
|
|
|
// Expire check
|
|
$this->assertArrayHasKey('expire', $tokens);
|
|
$this->assertNotEmpty($tokens['expire']);
|
|
$this->assertEquals(
|
|
DateTime::formatTz($response['body']['expire']),
|
|
$tokens['expire']
|
|
);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'url' => 'localhost/recovery',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'url' => 'http://remotehost/recovery',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => 'not-found@localhost.test',
|
|
'url' => 'http://localhost/recovery',
|
|
]);
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
}
|
|
|
|
#[Retry(count: 1)]
|
|
public function testUpdateAccountRecovery(): void
|
|
{
|
|
$data = $this->setupAccountWithRecovery();
|
|
$id = $data['id'];
|
|
$recovery = $data['recovery'];
|
|
$newPassword = 'test-recovery';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $recovery,
|
|
'password' => $newPassword,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::custom('ewewe'),
|
|
'secret' => $recovery,
|
|
'password' => $newPassword,
|
|
]);
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => 'sdasdasdasd',
|
|
'password' => $newPassword,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testSessionAlert(): void
|
|
{
|
|
$email = uniqid() . 'session-alert@appwrite.io';
|
|
$password = 'password123';
|
|
$name = 'Session Alert Tester';
|
|
|
|
// Enable session alerts
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/session-alerts', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'alerts' => true,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
// Create a new account
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? ''
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'name' => $name,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
// Create first session for the new account
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
// Create second session for the new account
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
|
|
// Check the alert email
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
|
|
$this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
|
|
$this->assertStringContainsString('Security alert: new session', $lastEmail['subject']);
|
|
$this->assertStringContainsString($response['body']['ip'], $lastEmail['text']); // IP Address
|
|
$this->assertStringContainsString('Unknown', $lastEmail['text']); // Country
|
|
$this->assertStringContainsString($response['body']['clientName'], $lastEmail['text']); // Client name
|
|
$this->assertStringNotContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']);
|
|
|
|
// Verify no alert sent in OTP login
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => 'otpuser3@appwrite.io'
|
|
]);
|
|
|
|
$this->assertEquals($response['headers']['status-code'], 201);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertNotEmpty($response['body']['$createdAt']);
|
|
$this->assertNotEmpty($response['body']['userId']);
|
|
$this->assertNotEmpty($response['body']['expire']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertEmpty($response['body']['phrase']);
|
|
$this->assertStringContainsStringIgnoringCase('New login detected on '. $this->getProject()['name'], $lastEmail['text']);
|
|
|
|
$userId = $response['body']['userId'];
|
|
|
|
$lastEmail = $this->getLastEmailByAddress('otpuser3@appwrite.io');
|
|
|
|
$this->assertNotEmpty($lastEmail, 'Email not found for address: otpuser3@appwrite.io');
|
|
$this->assertEquals('OTP for ' . $this->getProject()['name'] . ' Login', $lastEmail['subject']);
|
|
|
|
// Find 6 concurrent digits in email text - OTP
|
|
preg_match_all("/\b\d{6}\b/", $lastEmail['text'], $matches);
|
|
$code = ($matches[0] ?? [])[0] ?? '';
|
|
|
|
$this->assertNotEmpty($code);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => $userId,
|
|
'secret' => $code
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertEquals($userId, $response['body']['userId']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertNotEmpty($response['body']['expire']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
|
|
$lastEmailId = $lastEmail['id'];
|
|
$lastEmail = $this->getLastEmailByAddress('otpuser3@appwrite.io');
|
|
$this->assertEquals($lastEmailId, $lastEmail['id']);
|
|
}
|
|
|
|
public function testCreateOAuth2AccountSession(): void
|
|
{
|
|
// Just ensure we have a session set up
|
|
$this->setupAccountWithSession();
|
|
|
|
$provider = 'mock';
|
|
$appId = '1';
|
|
$secret = '123456';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'appId' => $appId,
|
|
'secret' => $secret,
|
|
'enabled' => true,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
|
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals('success', $response['body']['result']);
|
|
|
|
/**
|
|
* Test for Failure when disabled
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'appId' => $appId,
|
|
'secret' => $secret,
|
|
'enabled' => false,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
|
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
|
]);
|
|
|
|
$this->assertEquals(412, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateOidcOAuth2Token(): void
|
|
{
|
|
$provider = 'oidc';
|
|
$appId = '1';
|
|
|
|
// Valid well-known configuration
|
|
$secret = '{
|
|
"wellKnownEndpoint": "https://accounts.google.com/.well-known/openid-configuration",
|
|
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
|
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
|
"userinfoEndpoint": "https://openidconnect.googleapis.com/v1/userinfo"
|
|
}';
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'appId' => $appId,
|
|
'secret' => $secret,
|
|
'enabled' => true,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/tokens/oauth2/' . $provider, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
|
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
|
], true, false);
|
|
|
|
$this->assertEquals(301, $response['headers']['status-code']);
|
|
|
|
// Invalid well-known configuration
|
|
$secret = '{}';
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'appId' => $appId,
|
|
'secret' => $secret,
|
|
'enabled' => true,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/tokens/oauth2/' . $provider, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
|
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
|
]);
|
|
|
|
$this->assertEquals(500, $response['headers']['status-code']);
|
|
|
|
// Clean up - disable the OIDC provider to avoid polluting other parallel tests
|
|
$this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'enabled' => false,
|
|
]);
|
|
}
|
|
|
|
public function testBlockedAccount(): void
|
|
{
|
|
$email = uniqid() . 'user@localhost.test';
|
|
$password = 'password';
|
|
$name = 'User Name (blocked)';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'name' => $name,
|
|
]);
|
|
|
|
$id = $response['body']['$id'];
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$sessionId = $response['body']['$id'];
|
|
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/users/' . $id . '/status', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
], [
|
|
'status' => false,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
|
|
public function testSelfBlockedAccount(): void
|
|
{
|
|
$email = uniqid() . 'user55@localhost.test';
|
|
$password = 'password';
|
|
$name = 'User Name (self blocked)';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'name' => $name,
|
|
]);
|
|
|
|
$id = $response['body']['$id'];
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/status', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
], [
|
|
'status' => false,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertStringContainsString('a_session_' . $this->getProject()['$id'] . '=deleted', $response['headers']['set-cookie']);
|
|
$this->assertEquals('[]', $response['headers']['x-fallback-cookies']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateJWT(): void
|
|
{
|
|
$email = uniqid() . 'user@localhost.test';
|
|
$password = 'password';
|
|
$name = 'User Name (JWT)';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'name' => $name,
|
|
]);
|
|
|
|
$id = $response['body']['$id'];
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$sessionId = $response['body']['$id'];
|
|
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/jwt', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertEquals(119, $response['headers']['x-ratelimit-remaining']);
|
|
$this->assertNotEmpty($response['body']['jwt']);
|
|
$this->assertIsString($response['body']['jwt']);
|
|
|
|
$jwt = $response['body']['jwt'];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-jwt' => 'wrong-token',
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-jwt' => $jwt,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_DELETE, '/account/sessions/' . $sessionId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(204, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-jwt' => $jwt,
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
// Test JWT with custom duration
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/jwt', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'duration' => 5
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['jwt']);
|
|
|
|
$jwt = $response['body']['jwt'];
|
|
|
|
// Ensure JWT works before expiration
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-jwt' => $jwt,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
// Wait for JWT to expire
|
|
\sleep(6);
|
|
|
|
// Ensure JWT no longer works after expiration
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-jwt' => $jwt,
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateAnonymousAccount(): void
|
|
{
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
|
|
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
\usleep(1000 * 30); // wait for 30ms to let the shutdown update accessedAt
|
|
|
|
$apiKey = $this->getProject()['apiKey'];
|
|
$userId = $response['body']['userId'];
|
|
$response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $apiKey,
|
|
]));
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertArrayHasKey('accessedAt', $response['body']);
|
|
|
|
$this->assertNotEmpty($response['body']['accessedAt']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateAnonymousAccountVerification(): void
|
|
{
|
|
$session = $this->createAnonymousSession();
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'url' => 'http://localhost/verification',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['body']['code']);
|
|
$this->assertEquals('user_email_not_found', $response['body']['type']);
|
|
}
|
|
|
|
public function testUpdateAnonymousAccountPassword(): void
|
|
{
|
|
$session = $this->createAnonymousSession();
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'oldPassword' => '',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateAnonymousAccountEmail(): void
|
|
{
|
|
$session = $this->createAnonymousSession();
|
|
$email = uniqid() . 'new@localhost.test';
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'email' => $email,
|
|
'password' => '',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testConvertAnonymousAccount(): void
|
|
{
|
|
$session = $this->createAnonymousSession();
|
|
$email = uniqid() . 'new@localhost.test';
|
|
$password = 'new-password';
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => $password
|
|
]);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(409, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$email = uniqid() . 'new@localhost.test';
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $email);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'url' => 'http://localhost'
|
|
]);
|
|
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testConvertAnonymousAccountOAuth2(): void
|
|
{
|
|
// Clean up any existing user with the mock OAuth email to prevent
|
|
// conflicts with parallel tests that also use the mock provider
|
|
$this->deleteUserByEmail('useroauth@localhost.test');
|
|
|
|
$session = $this->createAnonymousSession();
|
|
$provider = 'mock';
|
|
$appId = '1';
|
|
$secret = '123456';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$userId = $response['body']['$id'] ?? '';
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'appId' => $appId,
|
|
'secret' => $secret,
|
|
'enabled' => true,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
// Delete any user with the mock OAuth email right before the OAuth call
|
|
// to minimize the race window with parallel tests
|
|
$this->deleteUserByEmail('useroauth@localhost.test');
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
|
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
$sessionCookieKey = 'a_session_' . $this->getProject()['$id'];
|
|
$this->assertArrayHasKey(
|
|
$sessionCookieKey,
|
|
$response['cookies'],
|
|
"Failed asserting that session cookie '$sessionCookieKey' is set. Cookies: " . json_encode($response['cookies'])
|
|
);
|
|
$session = $response['cookies'][$sessionCookieKey];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals($response['body']['$id'], $userId);
|
|
$this->assertEquals('User Name', $response['body']['name']);
|
|
$this->assertEquals('useroauth@localhost.test', $response['body']['email']);
|
|
|
|
// Since we only support one oauth user, let's also check updateSession here
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertEquals('123456', $response['body']['providerAccessToken']);
|
|
$this->assertEquals('tuvwxyz', $response['body']['providerRefreshToken']);
|
|
$this->assertGreaterThan(DateTime::addSeconds(new \DateTime(), 14400 - 5), $response['body']['providerAccessTokenExpiry']); // 5 seconds allowed networking delay
|
|
|
|
$initialExpiry = $response['body']['providerAccessTokenExpiry'];
|
|
|
|
sleep(3);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/sessions/current', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals('123456', $response['body']['providerAccessToken']);
|
|
$this->assertEquals('tuvwxyz', $response['body']['providerRefreshToken']);
|
|
$this->assertNotEquals($initialExpiry, $response['body']['providerAccessTokenExpiry']);
|
|
|
|
// Clean up - delete the user
|
|
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
]));
|
|
|
|
$this->assertEquals(204, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testOAuthUnverifiedEmailCannotLinkToExistingAccount(): void
|
|
{
|
|
$provider = 'mock-unverified';
|
|
$appId = '1';
|
|
$secret = '123456';
|
|
|
|
// First, create a user with the same email that the unverified OAuth will try to use
|
|
$email = 'useroauthunverified@localhost.test';
|
|
$password = 'password';
|
|
|
|
// Clean up any existing user with this email from parallel tests
|
|
$this->deleteUserByEmail($email);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$existingUserId = $response['body']['$id'];
|
|
|
|
// Enable the mock-unverified provider
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'appId' => $appId,
|
|
'secret' => $secret,
|
|
'enabled' => true,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
// Attempt OAuth login with unverified email - should fail because existing user has same email
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
|
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
$this->assertEquals('failure', $response['body']['result']);
|
|
|
|
// Clean up - delete the user
|
|
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $existingUserId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
]));
|
|
|
|
$this->assertEquals(204, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testOAuthVerifiedEmailCanLinkToExistingAccount(): void
|
|
{
|
|
$provider = 'mock';
|
|
$appId = '1';
|
|
$secret = '123456';
|
|
$email = 'useroauth@localhost.test';
|
|
|
|
// Clean up any existing user with this email from parallel tests
|
|
$this->deleteUserByEmail($email);
|
|
|
|
// Create a user with the same email that the verified OAuth will try to use
|
|
$response = $this->client->call(Client::METHOD_POST, '/account', [
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => 'password',
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$existingUserId = $response['body']['$id'];
|
|
|
|
// Enable the mock provider
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'provider' => $provider,
|
|
'appId' => $appId,
|
|
'secret' => $secret,
|
|
'enabled' => true,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
// Attempt OAuth login with verified email - should succeed and link to existing account
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
|
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals('success', $response['body']['result']);
|
|
|
|
// Verify the OAuth identity was linked to the existing user
|
|
$sessionCookieKey = 'a_session_' . $this->getProject()['$id'];
|
|
$session = $response['cookies'][$sessionCookieKey];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals($existingUserId, $response['body']['$id']);
|
|
$this->assertEquals($email, $response['body']['email']);
|
|
|
|
// Clean up - delete the user
|
|
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $existingUserId, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
]));
|
|
|
|
$this->assertEquals(204, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testGetSessionByID(): void
|
|
{
|
|
$session = $this->createAnonymousSession();
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertEquals('anonymous', $response['body']['provider']);
|
|
|
|
$sessionID = $response['body']['$id'];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/' . $sessionID, array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertEquals('anonymous', $response['body']['provider']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/97823askjdkasd80921371980', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateAccountNameSearch(): void
|
|
{
|
|
$data = $this->setupAccountWithUpdatedName();
|
|
$id = $data['id'];
|
|
$newName = 'Lorem';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/users', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
], [
|
|
'search' => $newName,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['users']);
|
|
// In parallel execution, there may be more users with name 'Lorem'
|
|
$this->assertGreaterThanOrEqual(1, count($response['body']['users']));
|
|
// Find our user in the results
|
|
$foundUser = null;
|
|
foreach ($response['body']['users'] as $user) {
|
|
if ($user['$id'] === $id) {
|
|
$foundUser = $user;
|
|
break;
|
|
}
|
|
}
|
|
$this->assertNotNull($foundUser, 'User should be found in search results');
|
|
$this->assertEquals($newName, $foundUser['name']);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/users', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
], [
|
|
'search' => $id,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['users']);
|
|
$this->assertCount(1, $response['body']['users']);
|
|
$this->assertEquals($newName, $response['body']['users'][0]['name']);
|
|
}
|
|
|
|
public function testUpdateAccountEmailSearch(): void
|
|
{
|
|
$data = $this->setupAccountWithUpdatedEmail();
|
|
$id = $data['id'];
|
|
$email = $data['email'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_GET, '/users', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
], [
|
|
'search' => '"' . $email . '"',
|
|
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['users']);
|
|
$this->assertCount(1, $response['body']['users']);
|
|
$this->assertEquals($response['body']['users'][0]['email'], $email);
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/users', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
], [
|
|
'search' => $id,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['users']);
|
|
$this->assertCount(1, $response['body']['users']);
|
|
$this->assertEquals($response['body']['users'][0]['email'], $email);
|
|
}
|
|
|
|
public function testCreatePhone(): void
|
|
{
|
|
$number = '+123456789';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'phone' => $number,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
|
|
|
$userId = $response['body']['userId'];
|
|
|
|
$smsRequest = $this->getLastRequestForProject(
|
|
$this->getProject()['$id'],
|
|
Scope::REQUEST_TYPE_SMS,
|
|
[
|
|
'header_X-Username' => 'username',
|
|
'header_X-Key' => 'password',
|
|
'method' => 'POST',
|
|
],
|
|
probe: function (array $request) use ($number) {
|
|
$this->assertEquals('Appwrite Mock Message Sender', $request['headers']['User-Agent'] ?? null);
|
|
$this->assertEquals('username', $request['headers']['X-Username'] ?? null);
|
|
$this->assertEquals('password', $request['headers']['X-Key'] ?? null);
|
|
$this->assertEquals('POST', $request['method'] ?? null);
|
|
$this->assertEquals('+123456789', $request['data']['from'] ?? null);
|
|
$this->assertEquals($number, $request['data']['to'] ?? null);
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique()
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateSessionWithPhone(): void
|
|
{
|
|
$data = $this->setupPhoneAccount();
|
|
$id = $data['id'];
|
|
$token = explode(" ", $data['token'])[0] ?? '';
|
|
$number = $data['number'];
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::custom('ewewe'),
|
|
'secret' => $token,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => 'sdasdasdasd',
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $token,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertNotEmpty($response['body']['userId']);
|
|
|
|
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['phone'], $number);
|
|
$this->assertTrue($response['body']['phoneVerification']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $token,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testConvertPhoneToPassword(): void
|
|
{
|
|
$data = $this->setupPhoneSession();
|
|
$session = $data['session'];
|
|
$email = uniqid() . 'new@localhost.test';
|
|
$password = 'new-password';
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $email);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => $password,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdatePhone(): void
|
|
{
|
|
$data = $this->setupPhoneConvertedToPassword();
|
|
$newPhone = '+456' . substr(str_replace('.', '', microtime(true)) . getmypid() . random_int(100, 999), -8);
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'phone' => $newPhone,
|
|
'password' => 'new-password'
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['phone'], $newPhone);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), []);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateSession(): void
|
|
{
|
|
$data = $this->setupPhoneUpdated();
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/users/' . $data['id'] . '/tokens', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
], [
|
|
'expire' => 60
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
$userId = $response['body']['userId'];
|
|
$secret = $response['body']['secret'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], [
|
|
'userId' => $userId,
|
|
'secret' => $secret
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertEquals($data['id'], $response['body']['userId']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertNotEmpty($response['body']['expire']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertEquals('browser', $response['body']['clientType']);
|
|
$this->assertEquals('CH', $response['body']['clientCode']);
|
|
$this->assertEquals('Chrome', $response['body']['clientName']);
|
|
|
|
// Forwarded User Agent with API Key
|
|
$response = $this->client->call(Client::METHOD_POST, '/users/' . $data['id'] . '/tokens', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
], [
|
|
'expire' => 60
|
|
]);
|
|
|
|
$userId = $response['body']['userId'];
|
|
$secret = $response['body']['secret'];
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
'x-forwarded-user-agent' => 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
|
|
], [
|
|
'userId' => $userId,
|
|
'secret' => $secret
|
|
]);
|
|
|
|
$this->assertEquals('browser', $response['body']['clientType']);
|
|
$this->assertEquals('CM', actual: $response['body']['clientCode']);
|
|
$this->assertEquals('Chrome Mobile', $response['body']['clientName']);
|
|
|
|
// Forwarded User Agent without API Key
|
|
$response = $this->client->call(Client::METHOD_POST, '/users/' . $data['id'] . '/tokens', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
|
], [
|
|
'expire' => 60
|
|
]);
|
|
|
|
$userId = $response['body']['userId'];
|
|
$secret = $response['body']['secret'];
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-forwarded-user-agent' => 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
|
|
], [
|
|
'userId' => $userId,
|
|
'secret' => $secret
|
|
]);
|
|
|
|
$this->assertEquals('browser', $response['body']['clientType']);
|
|
$this->assertEquals('CH', $response['body']['clientCode']);
|
|
$this->assertEquals('Chrome', $response['body']['clientName']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
// Invalid userId
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], [
|
|
'userId' => ID::custom('ewewe'),
|
|
'secret' => $secret,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
// Invalid secret
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], [
|
|
'userId' => $userId,
|
|
'secret' => '123456',
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testPhoneVerification(): void
|
|
{
|
|
$data = $this->setupPhoneUpdated();
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertNotEmpty($response['body']['$createdAt']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
|
|
|
$tokenCreatedAt = $response['body']['$createdAt'];
|
|
|
|
$phone = $data['phone'] ?? '';
|
|
$smsQuery = [
|
|
'header_X-Username' => 'username',
|
|
'header_X-Key' => 'password',
|
|
'method' => 'POST',
|
|
];
|
|
|
|
$smsRequest = $this->getLastRequestForProject(
|
|
$this->getProject()['$id'],
|
|
Scope::REQUEST_TYPE_SMS,
|
|
$smsQuery,
|
|
probe: function (array $request) use ($tokenCreatedAt, $phone) {
|
|
$this->assertArrayHasKey('data', $request);
|
|
$this->assertArrayHasKey('time', $request);
|
|
$this->assertArrayHasKey('message', $request['data'], "Last request missing message: " . \json_encode($request));
|
|
if (!empty($phone)) {
|
|
$this->assertEquals($phone, $request['data']['to'] ?? null);
|
|
}
|
|
|
|
// Ensure we are not using token from last sms login
|
|
$tokenRecievedAt = $request['time'];
|
|
$this->assertGreaterThan($tokenCreatedAt, $tokenRecievedAt);
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
|
|
// disable phone sessions
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => 'console',
|
|
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
|
]), [
|
|
'status' => false,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals(false, $response['body']['authPhone']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(501, $response['headers']['status-code']);
|
|
$this->assertEquals("Phone authentication is disabled for this project", $response['body']['message']);
|
|
|
|
// Re-enable phone auth so other parallel tests are not affected
|
|
$this->ensurePhoneAuthEnabled();
|
|
}
|
|
|
|
public function testUpdatePhoneVerification(): void
|
|
{
|
|
$data = $this->setupPhoneVerification();
|
|
$id = $data['id'];
|
|
$session = $data['session'];
|
|
$secret = $data['token'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $secret,
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'userId' => ID::custom('ewewe'),
|
|
'secret' => $secret,
|
|
]);
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => '999999',
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateMagicUrl(): void
|
|
{
|
|
// Use uniqid for uniqueness in parallel test execution
|
|
$email = 'magic-' . uniqid() . '-' . \time() . '@appwrite.io';
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
// 'url' => 'http://localhost/magiclogin',
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
$this->assertEmpty($response['body']['phrase']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
|
|
|
$userId = $response['body']['userId'];
|
|
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
$this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
|
|
$this->assertEquals($this->getProject()['name'] . ' Login', $lastEmail['subject']);
|
|
$this->assertStringContainsStringIgnoringCase('Sign in to '. $this->getProject()['name'] . ' with your secure link. Expires in 1 hour.', $lastEmail['text']);
|
|
$this->assertStringNotContainsStringIgnoringCase('security phrase', $lastEmail['text']);
|
|
|
|
$token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64);
|
|
|
|
$expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0);
|
|
|
|
$this->assertNotFalse($expireTime);
|
|
|
|
$secretTest = strpos($lastEmail['text'], 'secret=' . $response['body']['secret'], 0);
|
|
|
|
$this->assertNotFalse($secretTest);
|
|
|
|
$userIDTest = strpos($lastEmail['text'], 'userId=' . $response['body']['userId'], 0);
|
|
|
|
$this->assertNotFalse($userIDTest);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'url' => 'localhost/magiclogin',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'url' => 'http://remotehost/magiclogin',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'phrase' => true
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertNotEmpty($response['body']['phrase']);
|
|
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
$this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
|
|
$this->assertStringContainsStringIgnoringCase($response['body']['phrase'], $lastEmail['text']);
|
|
}
|
|
|
|
public function testCreateSessionWithMagicUrl(): void
|
|
{
|
|
$projectId = $this->getProject()['$id'];
|
|
|
|
// Get a fresh magic URL token - the cached one may have been consumed by setupMagicUrlSession
|
|
$email = \uniqid() . 'magicurl@localhost.test';
|
|
|
|
$tokenResponse = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
]);
|
|
|
|
$this->assertEquals(201, $tokenResponse['headers']['status-code']);
|
|
$id = $tokenResponse['body']['userId'];
|
|
|
|
$lastEmail = $this->getLastEmailByAddress($email);
|
|
$this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
|
|
$token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64);
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $projectId,
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => $token,
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertNotEmpty($response['body']['userId']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
|
|
$sessionId = $response['body']['$id'];
|
|
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
|
|
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]));
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $email);
|
|
$this->assertTrue($response['body']['emailVerification']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => ID::custom('ewewe'),
|
|
'secret' => $token,
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'userId' => $id,
|
|
'secret' => 'sdasdasdasd',
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateAccountPasswordWithMagicUrl(): void
|
|
{
|
|
$data = $this->setupMagicUrlSession();
|
|
$email = $data['email'];
|
|
$session = $data['session'];
|
|
|
|
/**
|
|
* Test for SUCCESS
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'password' => 'new-password'
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertIsArray($response['body']);
|
|
$this->assertNotEmpty($response['body']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
|
$this->assertEquals($response['body']['email'], $email);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]), [
|
|
'email' => $email,
|
|
'password' => 'new-password',
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertEmpty($response['body']['secret']);
|
|
|
|
/**
|
|
* Test for FAILURE
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]));
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), []);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Existing user tries to update password by passing wrong old password -> SHOULD FAIL
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'password' => 'new-password',
|
|
'oldPassword' => 'wrong-password',
|
|
]);
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
/**
|
|
* Existing user tries to update password without passing old password -> SHOULD FAIL
|
|
*/
|
|
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
|
'origin' => 'http://localhost',
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
|
]), [
|
|
'password' => 'new-password'
|
|
]);
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreatePushTarget(): void
|
|
{
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/targets/push', \array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id']
|
|
], $this->getHeaders()), [
|
|
'targetId' => ID::unique(),
|
|
'identifier' => 'test-identifier',
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertEquals('test-identifier', $response['body']['identifier']);
|
|
}
|
|
|
|
public function testUpdatePushTarget(): void
|
|
{
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/targets/push', \array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'targetId' => ID::unique(),
|
|
'identifier' => 'test-identifier-2',
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['$id']);
|
|
$this->assertEquals('test-identifier-2', $response['body']['identifier']);
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/account/targets/'. $response['body']['$id'] .'/push', \array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'identifier' => 'test-identifier-updated',
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals('test-identifier-updated', $response['body']['identifier']);
|
|
$this->assertEquals(false, $response['body']['expired']);
|
|
}
|
|
|
|
public function testMFARecoveryCodeChallenge(): void
|
|
{
|
|
// Generate recovery codes using existing authenticated session
|
|
$response = $this->client->call(Client::METHOD_POST, '/account/mfa/recovery-codes', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), []);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertNotEmpty($response['body']['recoveryCodes']);
|
|
$recoveryCodes = $response['body']['recoveryCodes'];
|
|
$this->assertGreaterThan(0, count($recoveryCodes));
|
|
|
|
// Create recovery code challenge
|
|
$challenge = $this->client->call(Client::METHOD_POST, '/account/mfa/challenge', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'factor' => 'recoveryCode'
|
|
]);
|
|
|
|
$this->assertEquals(201, $challenge['headers']['status-code']);
|
|
$this->assertNotEmpty($challenge['body']['$id']);
|
|
$challengeId = $challenge['body']['$id'];
|
|
|
|
// Test SUCCESS: Verify with valid recovery code (this tests the bug fix)
|
|
$verification = $this->client->call(Client::METHOD_PUT, '/account/mfa/challenge', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'challengeId' => $challengeId,
|
|
'otp' => $recoveryCodes[0]
|
|
]);
|
|
|
|
$this->assertEquals(200, $verification['headers']['status-code']);
|
|
$this->assertArrayHasKey('factors', $verification['body']);
|
|
$this->assertContains('recoveryCode', $verification['body']['factors']);
|
|
|
|
// Test that the code was consumed (can't use again)
|
|
$challenge2 = $this->client->call(Client::METHOD_POST, '/account/mfa/challenge', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'factor' => 'recoveryCode'
|
|
]);
|
|
|
|
$this->assertEquals(201, $challenge2['headers']['status-code']);
|
|
|
|
$verification2 = $this->client->call(Client::METHOD_PUT, '/account/mfa/challenge', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'challengeId' => $challenge2['body']['$id'],
|
|
'otp' => $recoveryCodes[0] // Same code should fail
|
|
]);
|
|
|
|
$this->assertEquals(401, $verification2['headers']['status-code']);
|
|
|
|
// Test FAILURE: Invalid recovery code
|
|
$challenge3 = $this->client->call(Client::METHOD_POST, '/account/mfa/challenge', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'factor' => 'recoveryCode'
|
|
]);
|
|
|
|
$this->assertEquals(201, $challenge3['headers']['status-code']);
|
|
|
|
$verification3 = $this->client->call(Client::METHOD_PUT, '/account/mfa/challenge', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'challengeId' => $challenge3['body']['$id'],
|
|
'otp' => 'invalid-code-123'
|
|
]);
|
|
|
|
$this->assertEquals(401, $verification3['headers']['status-code']);
|
|
}
|
|
}
|